[ty] Create fresh copies of generic callable typevars by dcreager · Pull Request #24949 · astral-sh/ruff (original) (raw)

Merged

dcreager

merged 32 commits into

Jun 8, 2026

Conversation

@dcreager

A generic callable binds new typevars. If that callable is used more than once in a particular expression, we should create separate (aka "fresh") copies of those typevars, so that the inferred types for each do not conflict with each other.

This comes up in two situations:

This PR updates these two places (call binding and signature assignability checking) to introduce fresh copies of the callable's typevars when needed.

@dcreager

@dcreager

@astral-sh-bot

Typing conformance results

No changes detected ✅

Current numbers
The percentage of diagnostics emitted that were expected errors held steady at 92.16%. The percentage of expected errors that received a diagnostic held steady at 87.31%. The number of fully passing files held steady at 92/134.

@astral-sh-bot

Memory usage report

Summary

Project Old New Diff Outcome
prefect 564.13MB 564.64MB +0.09% (520.04kB)
sphinx 207.45MB 207.60MB +0.07% (145.96kB)
trio 87.82MB 87.87MB +0.06% (52.14kB)
flake8 35.36MB 35.37MB +0.03% (9.24kB)

Significant changes

Click to expand detailed breakdown

prefect

Name Old New Diff Outcome
BoundTypeVarInstance 1.52MB 1.70MB +11.80% (183.16kB)
is_redundant_with_impl 1.93MB 1.99MB +2.71% (53.61kB)
InferableTypeVarsInner 185.24kB 230.27kB +24.31% (45.03kB)
when_constraint_set_assignable_to_owned_impl 3.04MB 3.07MB +1.00% (31.14kB)
GenericContext 305.89kB 334.38kB +9.31% (28.48kB)
infer_expression_types_impl 55.18MB 55.21MB +0.05% (26.02kB)
Type<'db>::apply_specialization_inner_::interned_arguments 2.78MB 2.80MB +0.71% (20.31kB)
infer_definition_types 75.91MB 75.93MB +0.03% (19.95kB)
Type<'db>::apply_specialization_inner_ 3.17MB 3.19MB +0.55% (17.89kB)
Specialization 2.50MB 2.51MB +0.56% (14.28kB)
infer_scope_types_impl 47.63MB 47.64MB +0.02% (8.91kB)
is_redundant_with_impl::interned_arguments 2.36MB 2.37MB +0.36% (8.59kB)
CallableType 2.71MB 2.72MB +0.29% (8.17kB)
inferable_typevars_inner 75.28kB 83.18kB +10.50% (7.91kB)
StaticClassLiteral<'db>::variance_of_::interned_arguments 85.92kB 90.98kB +5.89% (5.06kB)
... 34 more

sphinx

Name Old New Diff Outcome
BoundTypeVarInstance 624.16kB 695.78kB +11.47% (71.62kB)
when_constraint_set_assignable_to_owned_impl 1.49MB 1.50MB +0.78% (11.88kB)
InferableTypeVarsInner 76.11kB 87.45kB +14.90% (11.34kB)
GenericContext 140.11kB 149.59kB +6.77% (9.48kB)
infer_expression_types_impl 20.48MB 20.49MB +0.03% (6.55kB)
infer_definition_types 20.72MB 20.72MB +0.03% (5.45kB)
is_redundant_with_impl 914.20kB 919.57kB +0.59% (5.37kB)
Type<'db>::apply_specialization_inner_::interned_arguments 1.39MB 1.40MB +0.37% (5.31kB)
Type<'db>::apply_specialization_inner_ 1.51MB 1.52MB +0.30% (4.69kB)
Specialization 1.26MB 1.27MB +0.30% (3.86kB)
inferable_typevars_inner 36.83kB 38.69kB +5.05% (1.86kB)
is_redundant_with_impl::interned_arguments 1.14MB 1.14MB +0.12% (1.38kB)
StaticClassLiteral<'db>::try_mro_ 2.23MB 2.23MB +0.06% (1.30kB)
infer_statement_types_impl 460.65kB 461.46kB +0.18% (828.00B)
bound_typevar_default_type 16.15kB 16.92kB +4.74% (784.00B)
... 15 more

trio

Name Old New Diff Outcome
BoundTypeVarInstance 164.18kB 183.83kB +11.97% (19.65kB)
InferableTypeVarsInner 64.82kB 74.35kB +14.70% (9.53kB)
GenericContext 126.41kB 133.42kB +5.55% (7.01kB)
when_constraint_set_assignable_to_owned_impl 401.11kB 405.18kB +1.02% (4.08kB)
CallableType 666.52kB 668.57kB +0.31% (2.05kB)
infer_definition_types 6.61MB 6.61MB +0.02% (1.42kB)
inferable_typevars_inner 28.08kB 29.27kB +4.26% (1.20kB)
FunctionType 695.29kB 696.35kB +0.15% (1.06kB)
StaticClassLiteral<'db>::try_mro_ 714.82kB 715.82kB +0.14% (1016.00B)
bound_typevar_default_type 14.12kB 15.00kB +6.20% (896.00B)
FunctionType<'db>::signature_ 745.40kB 746.04kB +0.09% (656.00B)
Specialization 455.38kB 455.95kB +0.13% (592.00B)
cached_protocol_interface 140.38kB 140.93kB +0.39% (564.00B)
Type<'db>::apply_specialization_inner_ 558.77kB 559.16kB +0.07% (396.00B)
GenericAlias 186.05kB 186.40kB +0.19% (360.00B)
... 9 more

flake8

Name Old New Diff Outcome
BoundTypeVarInstance 45.70kB 50.78kB +11.11% (5.08kB)
GenericContext 44.95kB 46.52kB +3.48% (1.56kB)
when_constraint_set_assignable_to_owned_impl 157.64kB 159.03kB +0.88% (1.38kB)
InferableTypeVarsInner 22.81kB 24.02kB +5.33% (1.21kB)

@astral-sh-bot

ecosystem-analyzer results

Lint rule Added Removed Changed
invalid-argument-type 3 0 1
Total 3 0 1

Raw diff:

Expression (https://github.com/cognitedata/Expression)

xarray (https://github.com/pydata/xarray)

Full report with detailed diff (timing results)

@codspeed-hq

Merging this PR will degrade performance by 7.38%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 2 (👁 2) regressed benchmarks
✅ 65 untouched benchmarks
⏩ 60 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
👁 WallTime pydantic 9.9 s 10.5 s -5.76%
👁 Simulation DateType 226.7 ms 249.1 ms -8.98%

Comparing dcreager/alpha-renaming (dca5384) with main (b5fce66)

Open in CodSpeed

Footnotes

  1. 60 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

jelle-openai added a commit to jelle-openai/ruff that referenced this pull request

May 27, 2026

@jelle-openai

This was referenced

May 28, 2026

@dcreager

@dcreager

All of the ecosystem changes are exposing existing feature gaps in new locations. Though the changed diagnostic in xarray does show the intended fix from this PR — we now see distinct uses of the typevar of this recursive function.

@dcreager

The reduced performance seems reasonable for the new (and necessary) work that's being performed. There are similar slowdowns in a handful of ecosystem projects, but most of them are in line with the previous timing numbers.

@dcreager dcreager marked this pull request as ready for review

June 2, 2026 16:56

charliermarsh

/// Binds a legacy typevar with the generic context (class, function, type alias) that it is
/// being used in.
BindLegacyTypevars(BindingContext<'db>),
/// Freshens typevars bound by a generic context occurrence by adding a shared delta.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a "shared delta"? I guess I might've considered this the "nonce" itself? Can we give it a dedicated type?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're adding a value to the existing nonce to make sure that the result is unique. So if we have

Callable[[T'0, T'1], T'2]

(where each T is the same typevar, but with different nonces), we need to bump everything by 3 to make sure that the result doesn't conflict with any of the existing nonces:

Callable[[T'3, T'4], T'5]

charliermarsh

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes sense to me from reading the code and the summary, though I again don't feel super well positioned to give substantive feedback. I think that's fine, just want to call it out.

@charliermarsh

Should "freshen" just be "copy" or something?

@charliermarsh

I'm a little saddened by the memory increase here, though I can also try to reduce that hit separately.

@AlexWaygood

I think "freshen" is the terminology mypy uses for this too internally FWIW — don't know if that's something @dcreager was aware of before this PR or if he came to the same terminology independently 😆

@charliermarsh

If it's used elsewhere no objection from me for sure

@charliermarsh

Codex gave me this for reducing memory significantly: #25670

@charliermarsh

@dcreager

Codex gave me this for reducing memory significantly: #25670

Nice! Merged into here

@dcreager

I think "freshen" is the terminology mypy uses for this too internally FWIW — don't know if that's something @dcreager was aware of before this PR or if he came to the same terminology independently 😆

I didn't know about mypy using it, but it's a common term in the literature for this too!

@dcreager dcreager deleted the dcreager/alpha-renaming branch

June 8, 2026 19:33

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters

[ Show hidden characters]({{ revealButtonHref }})

Labels

ty

Multi-file analysis & type inference