Re: [PATCH v4 1/2] scripts: generate_rust_analyzer.py: add versioning infrastructure
From: Tamir Duberstein
Date: Wed Apr 29 2026 - 11:47:03 EST
On Wed, Apr 29, 2026 at 10:38 AM Jesung Yang <y.j3ms.n@xxxxxxxxx> wrote:
>
> On Tue, Apr 28, 2026 at 10:35 PM Jesung Yang <y.j3ms.n@xxxxxxxxx> wrote:
> > On Mon, Apr 27, 2026 at 2:23 PM Tamir Duberstein <tamird@xxxxxxxxxx> wrote:
> [...]
> > >
> > > ```
> > > T = t.TypeVar('T')
> > > if sys.version_info < (3, 10):
> > > def wrapper(func: t.Callable[..., T]) -> t.Callable[..., T]:
> > > def wrapper(*args: t.Any, **kwargs: t.Any) -> T:
> > > return func(*args, **kwargs)
> > > return wrapper
> > > else:
> > > P = t.ParamSpec('P')
> > >
> > > def wrapper(func: t.Callable[P, T]) -> t.Callable[P, T]:
> > > def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
> > > return func(*args, **kwargs)
> > > return wrapper
> > > ```
> > >
> > > This also works and type checks in both versions. Of course, the type
> > > information is discarded on 3.9, but as long as we run mypy on 3.10 we
> > > are OK.
> >
> > I'd go for the latter, since the former would require Python 3.9 users
> > to install an extra dependency to run `make LLVM=1 rust-analyzer`.
>
> OK, I did some more investigation and it appears that `ParamSpec` is
> generally intended for "preserve and forward" behavior; IIUC, we can't
> modify an arbitrary parameter type with it. To quote the docs:
>
> They are used to forward the parameter types of one callable to
> another callable -- a pattern commonly found in higher order functions
> and decorators [1].
>
> `Concatenate` seems to be one possible way to transform parameter types:
>
> `Concatenate` can be used in conjunction with `Callable` and
> `ParamSpec` to annotate a higher-order callable which adds, removes,
> or transforms parameters of another callable [2].
>
> However, it requires the parameter whose type we want to modify to
> occupy a fixed position. In our case, we would need to, for example,
> move the `deps` parameter to the first positional argument in each
> `append_*` function to ensure its placement is consistent across all of
> them.
>
> Given the above, I now think the simpler approach is to 1) modify
> the type of `deps` to `List[Optional[Dependency]]`, and 2) directly
> embed the filtering logic inside each `append_*` function:
>
> -------------------------------------------------
> def filter_deps(deps: List[Optional[Dependency]]) -> List[Dependency]:
> return [dep for dep in deps if dep is not None]
>
> def append_crate(
> ...,
> deps: List[Optional[Dependency]],
> ...,
> ) -> Dependency:
> return register_crate(
> build_crate(
> display_name,
> root_module,
> deps=filter_deps(deps),
> cfg=cfg,
> crate_attrs=crate_attrs,
> is_workspace_member=is_workspace_member,
> edition=edition,
> )
> )
>
> # ...
> -------------------------------------------------
>
> (The commit using the above approach can be found at [3].)
>
> Similar to the decorator approach, this requires less cleanup compared
> to the original `sysroot_deps` version, since we're only touching the
> function definitions rather than call sites.
>
> Of course we will need to revert the parameter type when the time comes,
> but in my view, this is much more straightforward than the decorator
> approach, which needs extra setup to work generally.
>
> What do you think?
>
> [1] https://docs.python.org/3/library/typing.html#typing.ParamSpec
> [2] https://docs.python.org/3/library/typing.html#typing.Concatenate
> [3] https://github.com/J3m3/linux/commit/ada5d5af9348
Seems ok to me. I would push the logic into build_crate to avoid
needing the `filter_deps` helper.
Another thought: where you have `# TODO: use `typing.NotRequired` when
Python 3.11 is adopted.` we should split the definition of the type
for < 3.11 so that we can get the correct (strict) type checking when
a more recent version of Python is used. We should get in the habit of
running type checking against all Python versions we support.
Cheers.
Tamir