Skip to content

Commit de391ee

Browse files
committed
fall back on default for unconstrained typevars
1 parent 8a0b4c2 commit de391ee

File tree

2 files changed

+51
-31
lines changed

2 files changed

+51
-31
lines changed

crates/ty_python_semantic/resources/mdtest/generics/specialize_constrained.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ from ty_extensions import ConstraintSet, generic_context
2222
# fmt: off
2323

2424
def unbounded[T]():
25-
# revealed: ty_extensions.Specialization[T@unbounded = object]
25+
# revealed: ty_extensions.Specialization[T@unbounded = Unknown]
2626
reveal_type(generic_context(unbounded).specialize_constrained(ConstraintSet.always()))
27+
# revealed: ty_extensions.Specialization[T@unbounded = object]
28+
reveal_type(generic_context(unbounded).specialize_constrained(ConstraintSet.range(Never, T, object)))
2729
# revealed: None
2830
reveal_type(generic_context(unbounded).specialize_constrained(ConstraintSet.never()))
2931

@@ -168,9 +170,11 @@ from typing import Any
168170
# fmt: off
169171

170172
def constrained_by_gradual[T: (Base, Any)]():
173+
# revealed: ty_extensions.Specialization[T@constrained_by_gradual = Base]
174+
reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.always()))
171175
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any]
172176
# revealed: ty_extensions.Specialization[T@constrained_by_gradual = object]
173-
reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.always()))
177+
reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.range(Never, T, object)))
174178
# revealed: None
175179
reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.never()))
176180

@@ -180,15 +184,13 @@ def constrained_by_gradual[T: (Base, Any)]():
180184
# revealed: ty_extensions.Specialization[T@constrained_by_gradual = Unrelated]
181185
reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.range(Never, T, Unrelated)))
182186

183-
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any]
184-
# revealed: ty_extensions.Specialization[T@constrained_by_gradual = Super]
187+
# revealed: ty_extensions.Specialization[T@constrained_by_gradual = Base]
185188
reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.range(Never, T, Super)))
186189
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any]
187190
# revealed: ty_extensions.Specialization[T@constrained_by_gradual = Super]
188191
reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.range(Super, T, Super)))
189192

190-
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any]
191-
# revealed: ty_extensions.Specialization[T@constrained_by_gradual = object]
193+
# revealed: ty_extensions.Specialization[T@constrained_by_gradual = Base]
192194
reveal_type(generic_context(constrained_by_gradual).specialize_constrained(ConstraintSet.range(Sub, T, object)))
193195
# TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual = Any]
194196
# revealed: ty_extensions.Specialization[T@constrained_by_gradual = Sub]
@@ -288,15 +290,15 @@ class Unrelated: ...
288290
# fmt: off
289291

290292
def mutually_bound[T: Base, U]():
291-
# revealed: ty_extensions.Specialization[T@mutually_bound = Base, U@mutually_bound = object]
293+
# revealed: ty_extensions.Specialization[T@mutually_bound = Base, U@mutually_bound = Unknown]
292294
reveal_type(generic_context(mutually_bound).specialize_constrained(ConstraintSet.always()))
293295
# revealed: None
294296
reveal_type(generic_context(mutually_bound).specialize_constrained(ConstraintSet.never()))
295297

296298
# revealed: ty_extensions.Specialization[T@mutually_bound = Base, U@mutually_bound = Base]
297299
reveal_type(generic_context(mutually_bound).specialize_constrained(ConstraintSet.range(Never, U, T)))
298300

299-
# revealed: ty_extensions.Specialization[T@mutually_bound = Sub, U@mutually_bound = object]
301+
# revealed: ty_extensions.Specialization[T@mutually_bound = Sub, U@mutually_bound = Unknown]
300302
reveal_type(generic_context(mutually_bound).specialize_constrained(ConstraintSet.range(Never, T, Sub)))
301303
# revealed: ty_extensions.Specialization[T@mutually_bound = Sub, U@mutually_bound = Sub]
302304
reveal_type(generic_context(mutually_bound).specialize_constrained(ConstraintSet.range(Never, T, Sub) & ConstraintSet.range(Never, U, T)))

crates/ty_python_semantic/src/types/constraints.rs

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1142,27 +1142,30 @@ impl<'db> Node<'db> {
11421142
/// Invokes a callback for each of the representative types of a particular typevar for this
11431143
/// constraint set.
11441144
///
1145-
/// There is a representative type for each distinct path from the BDD root to the `AlwaysTrue`
1145+
/// We first abstract the BDD so that it only mentions constraints on the requested typevar. We
1146+
/// then invoke your callback for each distinct path from the BDD root to the `AlwaysTrue`
11461147
/// terminal. Each of those paths can be viewed as the conjunction of the individual
11471148
/// constraints of each internal node that we traverse as we walk that path. We provide the
11481149
/// lower/upper bound of this conjunction to your callback, allowing you to choose any suitable
11491150
/// type in the range.
1151+
///
1152+
/// If the abstracted BDD does not mention the typevar at all (i.e., it leaves the typevar
1153+
/// completely unconstrained), we will invoke your callback once with `None`.
11501154
fn find_representative_types(
11511155
self,
11521156
db: &'db dyn Db,
11531157
bound_typevar: BoundTypeVarIdentity<'db>,
1154-
mut f: impl FnMut(Type<'db>, Type<'db>),
1158+
mut f: impl FnMut(Option<(Type<'db>, Type<'db>)>),
11551159
) {
11561160
self.retain_one(db, bound_typevar)
1157-
.find_representative_types_inner(db, Type::Never, Type::object(), &mut f);
1161+
.find_representative_types_inner(db, None, &mut f);
11581162
}
11591163

11601164
fn find_representative_types_inner(
11611165
self,
11621166
db: &'db dyn Db,
1163-
greatest_lower_bound: Type<'db>,
1164-
least_upper_bound: Type<'db>,
1165-
f: &mut dyn FnMut(Type<'db>, Type<'db>),
1167+
current_bounds: Option<(Type<'db>, Type<'db>)>,
1168+
f: &mut dyn FnMut(Option<(Type<'db>, Type<'db>)>),
11661169
) {
11671170
match self {
11681171
Node::AlwaysTrue => {
@@ -1172,12 +1175,16 @@ impl<'db> Node<'db> {
11721175
// If `lower ≰ upper`, then this path somehow represents in invalid specialization.
11731176
// That should have been removed from the BDD domain as part of the simplification
11741177
// process.
1175-
debug_assert!(greatest_lower_bound.is_subtype_of(db, least_upper_bound));
1178+
debug_assert!(current_bounds.is_none_or(
1179+
|(greatest_lower_bound, least_upper_bound)| {
1180+
greatest_lower_bound.is_subtype_of(db, least_upper_bound)
1181+
}
1182+
));
11761183

11771184
// We've been tracking the lower and upper bound that the types for this path must
11781185
// satisfy. Pass those bounds along and let the caller choose a representative type
11791186
// from within that range.
1180-
f(greatest_lower_bound, least_upper_bound);
1187+
f(current_bounds);
11811188
}
11821189

11831190
Node::AlwaysFalse => {
@@ -1186,6 +1193,9 @@ impl<'db> Node<'db> {
11861193
}
11871194

11881195
Node::Interior(interior) => {
1196+
let (greatest_lower_bound, least_upper_bound) =
1197+
current_bounds.unwrap_or((Type::Never, Type::object()));
1198+
11891199
// For an interior node, there are two outgoing paths: one for the `if_true`
11901200
// branch, and one for the `if_false` branch.
11911201
//
@@ -1200,8 +1210,7 @@ impl<'db> Node<'db> {
12001210
IntersectionType::from_elements(db, [least_upper_bound, constraint.upper(db)]);
12011211
interior.if_true(db).find_representative_types_inner(
12021212
db,
1203-
new_greatest_lower_bound,
1204-
new_least_upper_bound,
1213+
Some((new_greatest_lower_bound, new_least_upper_bound)),
12051214
f,
12061215
);
12071216

@@ -1217,8 +1226,7 @@ impl<'db> Node<'db> {
12171226
// path.
12181227
interior.if_false(db).find_representative_types_inner(
12191228
db,
1220-
greatest_lower_bound,
1221-
least_upper_bound,
1229+
Some((greatest_lower_bound, least_upper_bound)),
12221230
f,
12231231
);
12241232
}
@@ -3128,19 +3136,23 @@ impl<'db> GenericContext<'db> {
31283136
// _each_ of the paths into separate specializations, but it's not clear what we would
31293137
// do with that, so instead we just report the ambiguity as a specialization failure.
31303138
let mut satisfied = false;
3139+
let mut unconstrained = false;
31313140
let mut greatest_lower_bound = Type::Never;
31323141
let mut least_upper_bound = Type::object();
3133-
abstracted.find_representative_types(
3134-
db,
3135-
bound_typevar.identity(db),
3136-
|lower_bound, upper_bound| {
3137-
satisfied = true;
3138-
greatest_lower_bound =
3139-
UnionType::from_elements(db, [greatest_lower_bound, lower_bound]);
3140-
least_upper_bound =
3141-
IntersectionType::from_elements(db, [least_upper_bound, upper_bound]);
3142-
},
3143-
);
3142+
abstracted.find_representative_types(db, bound_typevar.identity(db), |bounds| {
3143+
satisfied = true;
3144+
match bounds {
3145+
Some((lower_bound, upper_bound)) => {
3146+
greatest_lower_bound =
3147+
UnionType::from_elements(db, [greatest_lower_bound, lower_bound]);
3148+
least_upper_bound =
3149+
IntersectionType::from_elements(db, [least_upper_bound, upper_bound]);
3150+
}
3151+
None => {
3152+
unconstrained = true;
3153+
}
3154+
}
3155+
});
31443156

31453157
// If there are no satisfiable paths in the BDD, then there is no valid specialization
31463158
// for this constraint set.
@@ -3150,6 +3162,12 @@ impl<'db> GenericContext<'db> {
31503162
return None;
31513163
}
31523164

3165+
// The BDD is satisfiable, but the typevar is unconstrained, then we use `None` to tell
3166+
// specialize_recursive to fall back on the typevar's default.
3167+
if unconstrained {
3168+
return None;
3169+
}
3170+
31533171
// If `lower ≰ upper`, then there is no type that satisfies all of the paths in the
31543172
// BDD. That's an ambiguous specialization, as described above.
31553173
if !greatest_lower_bound.is_subtype_of(db, least_upper_bound) {

0 commit comments

Comments
 (0)