Skip to content

Commit bdbe546

Browse files
committed
[phase 2] use solutions hook to promote literals
1 parent 7dcc1eb commit bdbe546

2 files changed

Lines changed: 49 additions & 77 deletions

File tree

crates/ty_python_semantic/src/types/call/bind.rs

Lines changed: 40 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3791,55 +3791,56 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
37913791
self.errors.extend(specialization_errors);
37923792

37933793
// Attempt to promote any promotable types assigned to the specialization.
3794-
let maybe_promote = |typevar: BoundTypeVarInstance<'db>, ty: Type<'db>| {
3795-
let bound_or_constraints = typevar.typevar(self.db).bound_or_constraints(self.db);
3796-
3797-
// For constrained TypeVars, the inferred type is already one of the
3798-
// constraints. Promoting literals would produce a type that doesn't
3799-
// match any constraint.
3800-
if matches!(
3801-
bound_or_constraints,
3802-
Some(TypeVarBoundOrConstraints::Constraints(_))
3803-
) {
3804-
return ty;
3805-
}
3794+
// The hook receives (typevar, lower_bound, upper_bound) and returns Some(ty) to
3795+
// override the default solution, or None to keep it.
3796+
let maybe_promote =
3797+
|typevar: BoundTypeVarInstance<'db>, lower: Type<'db>, _upper: Type<'db>| {
3798+
let bound_or_constraints = typevar.typevar(self.db).bound_or_constraints(self.db);
3799+
3800+
// For constrained TypeVars, the inferred type is already one of the
3801+
// constraints. Promoting literals would produce a type that doesn't
3802+
// match any constraint.
3803+
if matches!(
3804+
bound_or_constraints,
3805+
Some(TypeVarBoundOrConstraints::Constraints(_))
3806+
) {
3807+
return None;
3808+
}
38063809

3807-
let return_ty = self.constructor_instance_type.unwrap_or(self.return_ty);
3808-
let mut variance_in_return = TypeVarVariance::Bivariant;
3810+
let return_ty = self.constructor_instance_type.unwrap_or(self.return_ty);
3811+
let mut variance_in_return = TypeVarVariance::Bivariant;
38093812

3810-
// Find all occurrences of the type variable in the return type.
3811-
let visit_return_ty = |_, ty, variance, _| {
3812-
if ty != Type::TypeVar(typevar) {
3813-
return;
3814-
}
3813+
// Find all occurrences of the type variable in the return type.
3814+
let visit_return_ty = |_, ty, variance, _| {
3815+
if ty != Type::TypeVar(typevar) {
3816+
return;
3817+
}
38153818

3816-
variance_in_return = variance_in_return.join(variance);
3817-
};
3819+
variance_in_return = variance_in_return.join(variance);
3820+
};
38183821

3819-
return_ty.visit_specialization(self.db, self.call_expression_tcx, visit_return_ty);
3822+
return_ty.visit_specialization(self.db, self.call_expression_tcx, visit_return_ty);
38203823

3821-
// Promotion is only useful if the type variable is in invariant or contravariant
3822-
// position in the return type.
3823-
if variance_in_return.is_covariant() {
3824-
return ty;
3825-
}
3824+
// Promotion is only useful if the type variable is in invariant or contravariant
3825+
// position in the return type.
3826+
if variance_in_return.is_covariant() {
3827+
return None;
3828+
}
38263829

3827-
let promoted = ty.promote(self.db);
3830+
let promoted = lower.promote(self.db);
38283831

3829-
// If the TypeVar has an upper bound, only use the promoted type if it
3830-
// still satisfies the bound.
3831-
if let Some(TypeVarBoundOrConstraints::UpperBound(bound)) = bound_or_constraints {
3832-
if !promoted.is_assignable_to(self.db, bound) {
3833-
return ty;
3832+
// If the TypeVar has an upper bound, only use the promoted type if it
3833+
// still satisfies the bound.
3834+
if let Some(TypeVarBoundOrConstraints::UpperBound(bound)) = bound_or_constraints {
3835+
if !promoted.is_assignable_to(self.db, bound) {
3836+
return None;
3837+
}
38343838
}
3835-
}
38363839

3837-
promoted
3838-
};
3840+
Some(promoted)
3841+
};
38393842

3840-
let specialization = builder
3841-
.mapped(generic_context, maybe_promote)
3842-
.build(generic_context);
3843+
let specialization = builder.build_with(generic_context, maybe_promote);
38433844

38443845
self.return_ty = self.return_ty.apply_specialization(self.db, specialization);
38453846
self.specialization = Some(specialization);

crates/ty_python_semantic/src/types/generics.rs

Lines changed: 9 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1751,27 +1751,6 @@ impl<'db, 'c> SpecializationBuilder<'db, 'c> {
17511751
self.types
17521752
}
17531753

1754-
/// Map the types that have been assigned in this specialization.
1755-
pub(crate) fn mapped(
1756-
&self,
1757-
generic_context: GenericContext<'db>,
1758-
f: impl Fn(BoundTypeVarInstance<'db>, Type<'db>) -> Type<'db>,
1759-
) -> Self {
1760-
let mut types = self.types.clone();
1761-
for (identity, variable) in generic_context.variables_inner(self.db) {
1762-
if let Some(ty) = types.get_mut(identity) {
1763-
*ty = f(*variable, *ty);
1764-
}
1765-
}
1766-
1767-
Self {
1768-
db: self.db,
1769-
constraints: self.constraints,
1770-
inferable: self.inferable,
1771-
types,
1772-
}
1773-
}
1774-
17751754
pub(crate) fn with_default(
17761755
&self,
17771756
generic_context: GenericContext<'db>,
@@ -1805,24 +1784,20 @@ impl<'db, 'c> SpecializationBuilder<'db, 'c> {
18051784
/// Build a specialization, using a caller-provided hook to select the solution for each
18061785
/// typevar.
18071786
///
1808-
/// The `choose` hook is called for each typevar in the generic context with the typevar's
1809-
/// materialized lower and upper bounds:
1810-
/// - For typevars that were inferred (present in the type mappings), both bounds are set to
1811-
/// the inferred type (representing an equality constraint).
1812-
/// - For typevars that were not inferred, `lower` is `Never` and `upper` is `object`
1813-
/// (representing an unconstrained typevar).
1787+
/// The `choose` hook is called for each *inferred* typevar (those with entries in the type
1788+
/// mappings) with the typevar's materialized lower and upper bounds. Currently, both bounds
1789+
/// are set to the inferred type (representing an equality constraint). Unmapped typevars
1790+
/// are left to `specialize_recursive` to fill in with defaults.
18141791
///
18151792
/// The hook returns:
18161793
/// - `Some(ty)` to use `ty` as the specialization for this typevar
1817-
/// - `None` to use the default (the inferred type for mapped typevars, or the typevar's
1818-
/// default for unmapped typevars)
1794+
/// - `None` to use the inferred type unchanged
18191795
///
18201796
/// This method replaces the pattern of `mapped(...).build(...)`, allowing callers to
18211797
/// transform inferred types (e.g., literal promotion) in a single step. In the future,
18221798
/// when the builder's internal representation switches from a `HashMap` to a `ConstraintSet`,
18231799
/// the hook will receive actual lower/upper bounds from the constraint set instead of
18241800
/// synthetic equality bounds.
1825-
#[expect(dead_code)] // Will be used in Phase 2 of the constraint set migration
18261801
pub(crate) fn build_with(
18271802
&mut self,
18281803
generic_context: GenericContext<'db>,
@@ -1832,14 +1807,10 @@ impl<'db, 'c> SpecializationBuilder<'db, 'c> {
18321807
.variables_inner(self.db)
18331808
.iter()
18341809
.map(|(identity, variable)| {
1835-
if let Some(&mapped_ty) = self.types.get(identity) {
1836-
// The typevar was inferred — present both bounds as the inferred type.
1837-
let chosen = choose(*variable, mapped_ty, mapped_ty);
1838-
Some(chosen.unwrap_or(mapped_ty))
1839-
} else {
1840-
// The typevar was not inferred — present open bounds.
1841-
choose(*variable, Type::Never, Type::object())
1842-
}
1810+
let mapped_ty = self.types.get(identity).copied()?;
1811+
// The typevar was inferred — present both bounds as the inferred type.
1812+
let chosen = choose(*variable, mapped_ty, mapped_ty);
1813+
Some(chosen.unwrap_or(mapped_ty))
18431814
});
18441815

18451816
generic_context.specialize_recursive(self.db, types)

0 commit comments

Comments
 (0)