Skip to content

[ty] Avoid deriving sequents for typevars with concrete bounds - #27587

Merged
ibraheemdev merged 4 commits into
mainfrom
ibraheem/mixed-bounds-fast-path
Aug 12, 2026
Merged

[ty] Avoid deriving sequents for typevars with concrete bounds#27587
ibraheemdev merged 4 commits into
mainfrom
ibraheem/mixed-bounds-fast-path

Conversation

@ibraheemdev

@ibraheemdev ibraheemdev commented Aug 7, 2026

Copy link
Copy Markdown
Member

The constraint solver has a fast-path that bypasses the full solver when all type variables are bounded by concrete types. This was necessary to fix exponential blowup in the sequent map with a large number of type variables with no transitive relationships. astral-sh/ty#3989. However, introducing a single relationship between type variables bypasses the fast path, making it possible to observe similar exponential blowup, as seen in #26680. This PR adds a short-circuit in the full solver to avoid sequent discovery for the subset of typevars that have concrete bounds.

Note that I attempted to remove the fast-path entirely now that we have a more general fix, but it reintroduced a small regression due to the general extra work that the full solver performs. A followup may be able to close the gap entirely. There are some differences in behavior and diagnostics between the two paths that should eventually be addressed as well.

@ibraheemdev ibraheemdev added the ty Multi-file analysis & type inference label Aug 7, 2026
@astral-sh-bot

astral-sh-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown

Typing conformance results

No changes detected ✅

Current numbers
The percentage of diagnostics emitted that were expected errors held steady at 96.98%. The percentage of expected errors that received a diagnostic held steady at 93.62%. The number of fully passing files held steady at 106/133.

@astral-sh-bot

astral-sh-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown

Memory usage report

Summary

Project Old New Diff Outcome
prefect 449.14MB 449.29MB +0.03% (153.50kB)
sphinx 164.73MB 164.80MB +0.04% (72.66kB)
flake8 39.70MB 39.69MB -0.01% (3.14kB) ⬇️
trio 92.27MB 92.26MB -0.00% (4.41kB) ⬇️

Significant changes

Click to expand detailed breakdown

prefect

Name Old New Diff Outcome
when_constraint_set_assignable_to_owned_impl 5.32MB 5.54MB +4.16% (226.19kB)
infer_definition_types 34.55MB 34.48MB -0.19% (67.65kB) ⬇️
Type<'db>::cached_materialization_::interned_arguments 1.38MB 1.44MB +4.53% (63.91kB)
Type<'db>::cached_materialization_ 1.03MB 1.07MB +4.42% (46.43kB)
infer_expression_types_impl 23.31MB 23.27MB -0.15% (36.23kB) ⬇️
infer_scope_types_impl 18.80MB 18.78MB -0.10% (18.68kB) ⬇️
StaticClassLiteral<'db>::variance_of_owner_::interned_arguments 107.16kB 92.47kB -13.71% (14.70kB) ⬇️
is_possibly_constraint_set_assignable 350.08kB 339.11kB -3.13% (10.97kB) ⬇️
GenericAlias<'db>::variance_of_owner_::interned_arguments 79.66kB 72.36kB -9.17% (7.30kB) ⬇️
StaticClassLiteral<'db>::variance_of_owner_ 58.87kB 51.70kB -12.18% (7.17kB) ⬇️
StaticClassLiteral<'db>::try_mro_specialized_ 4.22MB 4.22MB -0.11% (4.81kB) ⬇️
CallableType 7.99MB 8.00MB +0.05% (4.26kB)
GenericAlias<'db>::variance_of_owner_ 39.76kB 36.02kB -9.41% (3.74kB) ⬇️
TypePair 7.74MB 7.73MB -0.04% (3.09kB) ⬇️
all_narrowing_constraints_for_expression 4.51MB 4.51MB -0.06% (2.84kB) ⬇️
... 32 more

sphinx

Name Old New Diff Outcome
when_constraint_set_assignable_to_owned_impl 1.41MB 1.46MB +3.43% (49.41kB)
Type<'db>::cached_materialization_::interned_arguments 375.08kB 421.95kB +12.50% (46.88kB)
Type<'db>::cached_materialization_ 246.98kB 275.85kB +11.69% (28.87kB)
StaticClassLiteral<'db>::variance_of_owner_::interned_arguments 45.55kB 36.52kB -19.81% (9.02kB) ⬇️
infer_expression_types_impl 11.53MB 11.52MB -0.06% (6.67kB) ⬇️
is_possibly_constraint_set_assignable 104.95kB 98.45kB -6.20% (6.51kB) ⬇️
infer_definition_types 10.95MB 10.95MB -0.06% (6.40kB) ⬇️
infer_scope_types_impl 6.08MB 6.08MB -0.08% (4.90kB) ⬇️
GenericAlias<'db>::variance_of_owner_::interned_arguments 31.54kB 27.07kB -14.17% (4.47kB) ⬇️
StaticClassLiteral<'db>::variance_of_owner_ 23.51kB 19.07kB -18.88% (4.44kB) ⬇️
StaticClassLiteral<'db>::try_mro_specialized_ 1.61MB 1.61MB -0.16% (2.60kB) ⬇️
GenericAlias<'db>::variance_of_owner_ 16.39kB 14.15kB -13.68% (2.24kB) ⬇️
all_narrowing_constraints_for_expression 2.25MB 2.25MB -0.07% (1.57kB) ⬇️
Specialization 1.58MB 1.57MB -0.07% (1.09kB) ⬇️
TypePair 2.12MB 2.12MB -0.04% (960.00B) ⬇️
... 24 more

flake8

Name Old New Diff Outcome
StaticClassLiteral<'db>::variance_of_owner_::interned_arguments 7.65kB 5.16kB -32.58% (2.49kB) ⬇️
when_constraint_set_assignable_to_owned_impl 158.34kB 160.76kB +1.53% (2.42kB)
Type<'db>::cached_materialization_::interned_arguments 67.19kB 69.22kB +3.02% (2.03kB)
Type<'db>::cached_materialization_ 42.51kB 43.73kB +2.87% (1.22kB)
StaticClassLiteral<'db>::variance_of_owner_ 3.73kB 2.54kB -32.01% (1.20kB) ⬇️
infer_expression_types_impl 763.70kB 762.71kB -0.13% (1008.00B) ⬇️
StaticClassLiteral<'db>::try_mro_specialized_ 258.09kB 257.36kB -0.28% (752.00B) ⬇️
TypePair 231.47kB 230.81kB -0.28% (672.00B) ⬇️
infer_definition_types 1.06MB 1.06MB -0.06% (648.00B) ⬇️
is_possibly_constraint_set_assignable 13.19kB 12.60kB -4.44% (600.00B) ⬇️
infer_scope_types_impl 537.44kB 537.09kB -0.07% (360.00B) ⬇️
GenericAlias<'db>::variance_of_owner_::interned_arguments 2.32kB 2.06kB -11.11% (264.00B) ⬇️
Specialization 249.92kB 249.67kB -0.10% (256.00B) ⬇️
StaticClassLiteral<'db>::try_mro_specialized_::interned_arguments 82.27kB 82.05kB -0.26% (216.00B) ⬇️
GenericAlias 112.92kB 112.78kB -0.12% (144.00B) ⬇️
... 11 more

trio

Name Old New Diff Outcome
when_constraint_set_assignable_to_owned_impl 968.73kB 1003.16kB +3.55% (34.43kB)
StaticClassLiteral<'db>::try_mro_specialized_ 808.14kB 796.59kB -1.43% (11.55kB) ⬇️
Type<'db>::cached_materialization_::interned_arguments 301.64kB 312.89kB +3.73% (11.25kB)
infer_expression_types_impl 5.22MB 5.21MB -0.19% (10.13kB) ⬇️
Type<'db>::cached_materialization_ 210.46kB 217.24kB +3.22% (6.78kB)
StaticClassLiteral<'db>::variance_of_owner_::interned_arguments 44.69kB 39.70kB -11.15% (4.98kB) ⬇️
Specialization 1.02MB 1.01MB -0.38% (3.94kB) ⬇️
infer_definition_types 4.44MB 4.43MB -0.08% (3.69kB) ⬇️
StaticClassLiteral<'db>::try_mro_specialized_::interned_arguments 265.50kB 261.91kB -1.35% (3.59kB) ⬇️
TypePair 966.09kB 963.38kB -0.28% (2.72kB) ⬇️
is_possibly_constraint_set_assignable 83.48kB 80.98kB -2.99% (2.50kB) ⬇️
GenericAlias 390.02kB 387.63kB -0.61% (2.39kB) ⬇️
StaticClassLiteral<'db>::variance_of_owner_ 21.84kB 19.45kB -10.95% (2.39kB) ⬇️
infer_scope_types_impl 2.94MB 2.94MB -0.04% (1.27kB) ⬇️
CallableType 1.39MB 1.39MB -0.09% (1.27kB) ⬇️
... 27 more

@astral-sh-bot

astral-sh-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown

ecosystem-analyzer results

No diagnostic changes detected ✅

Full report with detailed diff (timing results)

@codspeed-hq

codspeed-hq Bot commented Aug 7, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 171 untouched benchmarks
🆕 2 new benchmarks
⏩ 24 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
🆕 Simulation ty_micro[mixed_many_invariant_typevars] N/A 215.3 ms N/A
🆕 Memory ty_micro[mixed_many_invariant_typevars] N/A 8.1 MB N/A

Comparing ibraheem/mixed-bounds-fast-path (63ccea2) with main (5b2ed0d)

Open in CodSpeed

Footnotes

  1. 24 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.

@ibraheemdev
ibraheemdev force-pushed the ibraheem/mixed-bounds-fast-path branch 3 times, most recently from 31131d6 to 303dd22 Compare August 10, 2026 19:59
@ibraheemdev
ibraheemdev force-pushed the ibraheem/mixed-bounds-fast-path branch from 303dd22 to a8385c6 Compare August 10, 2026 20:33
@ibraheemdev
ibraheemdev marked this pull request as ready for review August 10, 2026 20:44
@ibraheemdev
ibraheemdev requested a review from a team as a code owner August 10, 2026 20:44
@astral-sh-bot
astral-sh-bot Bot requested a review from carljm August 10, 2026 20:44
///
/// Unlike [`any_over_type`], this reports when the traversal is incomplete because evaluating a
/// lazy type attribute would be required.
pub(super) fn try_any_over_type<'db>(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I pulled this in from #26869.

@carljm carljm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good! A few inline comments.

.iter()
.chain(constraint_support.iter())
.any(|typevar| self.independent_typevars.contains(&typevar))
&& !self.incomplete_support_constraints.contains(existing)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Changing the concrete argument in an eight-variable version of the benchmark to:

type AliasInt = int
x = Invariant[AliasInt]()

increases checking time from 0.03 s to 2.64 s; a fully specialized protocol, concrete TypedDict, or NewType takes about 5.2 s.

Do you think it would be worth a follow-up to extend this optimization to aliases, protocols, TypedDicts, or NewTypes, instead of conservatively always adding them to incomplete_support_constraints? I guess the main worry here is the potential for cycles or perf regressions resulting from following lazy attributes?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Created astral-sh/ty#4243 for the followup.

Comment thread crates/ty_python_semantic/src/types/constraints.rs Outdated
Comment thread crates/ruff_benchmark/benches/ty_constraint_set.rs
Comment thread crates/ty_python_semantic/src/types/visitor.rs Outdated
@ibraheemdev
ibraheemdev merged commit 9dea5ef into main Aug 12, 2026
108 of 112 checks passed
@ibraheemdev
ibraheemdev deleted the ibraheem/mixed-bounds-fast-path branch August 12, 2026 20:28
George-Ogden pushed a commit to George-Ogden/ruff that referenced this pull request Aug 16, 2026
…l-sh#27587)

The constraint solver has a fast-path that bypasses the full solver when
all type variables are bounded by concrete types. This was necessary to
fix exponential blowup in the sequent map with a large number of type
variables with no transitive relationships.
astral-sh/ty#3989. However, introducing a
single relationship between type variables bypasses the fast path,
making it possible to observe similar exponential blowup, as seen in
astral-sh#26680. This PR adds a
short-circuit in the full solver to avoid sequent discovery for the
subset of typevars that have concrete bounds.

Note that I attempted to remove the fast-path entirely now that we have
a more general fix, but it reintroduced a small regression due to the
general extra work that the full solver performs. A followup may be able
to close the gap entirely. There are some differences in behavior and
diagnostics between the two paths that should eventually be addressed as
well.

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants