@@ -1005,6 +1005,50 @@ pub struct TypeVarId;
10051005#[ derive( salsa:: Update , get_size2:: GetSize ) ]
10061006pub struct ConstraintId ;
10071007
1008+ #[ derive( Clone , Copy , Debug , Eq , Hash , PartialEq , salsa:: Update , get_size2:: GetSize ) ]
1009+ enum NestedSubstitutionSide {
1010+ Lower ,
1011+ Upper ,
1012+ }
1013+
1014+ /// Identifies one nested-typevar substitution that has been applied while saturating a single
1015+ /// BDD path.
1016+ ///
1017+ /// We intentionally key this by the constraint that we substitute _into_ and the typevar that we
1018+ /// substitute _for_, but not by the replacement type. For the pathological cases that matter for
1019+ /// performance, the same nested substitution shape can keep producing ever-deeper replacement
1020+ /// types (for instance, repeated `Iterable[...]` wrapping). Recording only the substitution site
1021+ /// lets [`PathAssignments`] apply that substitution at most once per path, which preserves the
1022+ /// initial cross-typevar relationship without repeatedly unfolding the same pattern.
1023+ #[ derive( Clone , Copy , Debug , Eq , Hash , PartialEq , salsa:: Update , get_size2:: GetSize ) ]
1024+ struct NestedSubstitution {
1025+ substituted_into : ConstraintId ,
1026+ substituted_typevar : TypeVarId ,
1027+ side : NestedSubstitutionSide ,
1028+ }
1029+
1030+ /// A constraint derived from the sequent map, optionally annotated with the nested substitution
1031+ /// step that produced it.
1032+ #[ derive( Clone , Copy , Debug , Eq , Hash , PartialEq , salsa:: Update , get_size2:: GetSize ) ]
1033+ struct DerivedConstraint {
1034+ constraint : ConstraintId ,
1035+ nested_substitution : Option < NestedSubstitution > ,
1036+ }
1037+
1038+ fn nested_substitution < ' db > (
1039+ db : & ' db dyn Db ,
1040+ builder : & ConstraintSetBuilder < ' db > ,
1041+ substituted_into : ConstraintId ,
1042+ substituted_typevar : BoundTypeVarInstance < ' db > ,
1043+ side : NestedSubstitutionSide ,
1044+ ) -> NestedSubstitution {
1045+ NestedSubstitution {
1046+ substituted_into,
1047+ substituted_typevar : builder. typevar_id ( db, substituted_typevar) ,
1048+ side,
1049+ }
1050+ }
1051+
10081052/// An individual constraint in a constraint set. This restricts a single typevar to be within a
10091053/// lower and upper bound.
10101054#[ derive( Clone , Copy , Debug , Eq , Hash , PartialEq , get_size2:: GetSize , salsa:: Update ) ]
@@ -4241,7 +4285,7 @@ struct SequentMap {
42414285 /// Sequents of the form `C₁ ∧ C₂ → false`
42424286 pair_impossibilities : FxHashSet < ( ConstraintId , ConstraintId ) > ,
42434287 /// Sequents of the form `C₁ ∧ C₂ → D`
4244- pair_implications : FxIndexMap < ( ConstraintId , ConstraintId ) , FxIndexSet < ConstraintId > > ,
4288+ pair_implications : FxIndexMap < ( ConstraintId , ConstraintId ) , FxIndexSet < DerivedConstraint > > ,
42454289 /// Sequents of the form `C → D`
42464290 single_implications : FxIndexMap < ConstraintId , FxIndexSet < ConstraintId > > ,
42474291}
@@ -4385,6 +4429,18 @@ impl SequentMap {
43854429 ante1 : ConstraintId ,
43864430 ante2 : ConstraintId ,
43874431 post : ConstraintId ,
4432+ ) {
4433+ self . add_pair_implication_with_provenance ( db, builder, ante1, ante2, post, None ) ;
4434+ }
4435+
4436+ fn add_pair_implication_with_provenance < ' db > (
4437+ & mut self ,
4438+ db : & ' db dyn Db ,
4439+ builder : & ConstraintSetBuilder < ' db > ,
4440+ ante1 : ConstraintId ,
4441+ ante2 : ConstraintId ,
4442+ post : ConstraintId ,
4443+ nested_substitution : Option < NestedSubstitution > ,
43884444 ) {
43894445 // If the post constraint is unsatisfiable, then the antecedents contradict each other.
43904446 let post_data = builder. constraint_data ( post) ;
@@ -4400,11 +4456,15 @@ impl SequentMap {
44004456 if ante1. implies ( db, builder, post) || ante2. implies ( db, builder, post) {
44014457 return ;
44024458 }
4459+ let derived = DerivedConstraint {
4460+ constraint : post,
4461+ nested_substitution,
4462+ } ;
44034463 if self
44044464 . pair_implications
44054465 . entry ( Self :: pair_key ( ante1, ante2) )
44064466 . or_default ( )
4407- . insert ( post )
4467+ . insert ( derived )
44084468 {
44094469 tracing:: trace!(
44104470 target: "ty_python_semantic::types::constraints::SequentMap" ,
@@ -4878,12 +4938,19 @@ impl SequentMap {
48784938 constrained_data. lower ,
48794939 new_upper,
48804940 ) ;
4881- self . add_pair_implication (
4941+ self . add_pair_implication_with_provenance (
48824942 db,
48834943 builder,
48844944 bound_constraint,
48854945 constrained_constraint,
48864946 post,
4947+ Some ( nested_substitution (
4948+ db,
4949+ builder,
4950+ constrained_constraint,
4951+ bound_typevar,
4952+ NestedSubstitutionSide :: Upper ,
4953+ ) ) ,
48874954 ) ;
48884955 }
48894956 }
@@ -4935,12 +5002,19 @@ impl SequentMap {
49355002 new_lower,
49365003 constrained_data. upper ,
49375004 ) ;
4938- self . add_pair_implication (
5005+ self . add_pair_implication_with_provenance (
49395006 db,
49405007 builder,
49415008 bound_constraint,
49425009 constrained_constraint,
49435010 post,
5011+ Some ( nested_substitution (
5012+ db,
5013+ builder,
5014+ constrained_constraint,
5015+ bound_typevar,
5016+ NestedSubstitutionSide :: Lower ,
5017+ ) ) ,
49445018 ) ;
49455019 }
49465020 }
@@ -5024,12 +5098,19 @@ impl SequentMap {
50245098 constrained_data. lower ,
50255099 new_upper,
50265100 ) ;
5027- self . add_pair_implication (
5101+ self . add_pair_implication_with_provenance (
50285102 db,
50295103 builder,
50305104 bound_constraint,
50315105 constrained_constraint,
50325106 post,
5107+ Some ( nested_substitution (
5108+ db,
5109+ builder,
5110+ constrained_constraint,
5111+ nested_typevar,
5112+ NestedSubstitutionSide :: Upper ,
5113+ ) ) ,
50335114 ) ;
50345115 }
50355116 }
@@ -5061,12 +5142,19 @@ impl SequentMap {
50615142 new_lower,
50625143 constrained_data. upper ,
50635144 ) ;
5064- self . add_pair_implication (
5145+ self . add_pair_implication_with_provenance (
50655146 db,
50665147 builder,
50675148 bound_constraint,
50685149 constrained_constraint,
50695150 post,
5151+ Some ( nested_substitution (
5152+ db,
5153+ builder,
5154+ constrained_constraint,
5155+ nested_typevar,
5156+ NestedSubstitutionSide :: Lower ,
5157+ ) ) ,
50705158 ) ;
50715159 }
50725160 }
@@ -5307,7 +5395,7 @@ impl SequentMap {
53075395 "{} ∧ {} → {}" ,
53085396 ante1. display( self . db, self . builder) ,
53095397 ante2. display( self . db, self . builder) ,
5310- post. display( self . db, self . builder) ,
5398+ post. constraint . display( self . db, self . builder) ,
53115399 ) ?;
53125400 }
53135401 }
@@ -5346,6 +5434,8 @@ impl SequentMap {
53465434pub ( crate ) struct PathAssignments {
53475435 map : SequentMap ,
53485436 assignments : FxIndexMap < ConstraintAssignment , usize > ,
5437+ /// Nested substitutions that we have already applied on the current root→terminal path.
5438+ nested_substitutions : FxIndexSet < NestedSubstitution > ,
53495439 /// Constraints that we have discovered, mapped to whether we have processed them yet. (This
53505440 /// ensures a stable order for all of the derived constraints that we create, while still
53515441 /// letting us create them lazily.)
@@ -5361,6 +5451,7 @@ impl PathAssignments {
53615451 Self {
53625452 map : SequentMap :: default ( ) ,
53635453 assignments : FxIndexMap :: default ( ) ,
5454+ nested_substitutions : FxIndexSet :: default ( ) ,
53645455 discovered,
53655456 }
53665457 }
@@ -5399,6 +5490,7 @@ impl PathAssignments {
53995490 // pass along the range of which assignments are new, and so that we can reset back to this
54005491 // point before returning.
54015492 let start = self . assignments . len ( ) ;
5493+ let nested_substitutions_start = self . nested_substitutions . len ( ) ;
54025494
54035495 // Add the new assignment and anything we can derive from it.
54045496 tracing:: trace!(
@@ -5440,6 +5532,8 @@ impl PathAssignments {
54405532 // Reset back to where we were before following this edge, so that the caller can reuse a
54415533 // single instance for the entire BDD traversal.
54425534 self . assignments . truncate ( start) ;
5535+ self . nested_substitutions
5536+ . truncate ( nested_substitutions_start) ;
54435537 result
54445538 }
54455539
@@ -5605,11 +5699,26 @@ impl PathAssignments {
56055699 let mut new_constraints = Vec :: new ( ) ;
56065700 for ( ( ante1, ante2) , posts) in & self . map . pair_implications {
56075701 for post in posts {
5608- if self . assignment_holds ( ante1. when_true ( ) )
5609- && self . assignment_holds ( ante2. when_true ( ) )
5702+ if ! self . assignment_holds ( ante1. when_true ( ) )
5703+ || ! self . assignment_holds ( ante2. when_true ( ) )
56105704 {
5611- new_constraints . push ( * post ) ;
5705+ continue ;
56125706 }
5707+
5708+ // Nested-typevar sequents are the mechanism that preserves cross-typevar facts when
5709+ // we later existentially quantify away one of the typevars. However, once we've
5710+ // applied a particular substitution site on the current path, reapplying it with a
5711+ // newly derived replacement type does not add fundamentally new information — it
5712+ // just keeps unfolding the same pattern one layer deeper. Skipping repeated
5713+ // applications here prevents those infinite-looking expansion chains while still
5714+ // keeping the first derived relationship.
5715+ if let Some ( nested_substitution) = post. nested_substitution
5716+ && !self . nested_substitutions . insert ( nested_substitution)
5717+ {
5718+ continue ;
5719+ }
5720+
5721+ new_constraints. push ( post. constraint ) ;
56135722 }
56145723 }
56155724
0 commit comments