Skip to content

Commit 1951f1b

Browse files
authored
[ty] fix panic when instantiating a type variable with invalid constraints (#21663)
1 parent 10de342 commit 1951f1b

File tree

8 files changed

+185
-81
lines changed

8 files changed

+185
-81
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class C[T: (A, B)]:
2+
def f(foo: T):
3+
try:
4+
pass
5+
except foo:
6+
pass

crates/ty_python_semantic/src/types.rs

Lines changed: 157 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ pub(crate) use crate::types::narrow::infer_narrowing_constraint;
6868
use crate::types::newtype::NewType;
6969
pub(crate) use crate::types::signatures::{Parameter, Parameters};
7070
use crate::types::signatures::{ParameterForm, walk_signature};
71-
use crate::types::tuple::{TupleSpec, TupleSpecBuilder};
71+
use crate::types::tuple::{Tuple, TupleSpec, TupleSpecBuilder};
7272
pub(crate) use crate::types::typed_dict::{TypedDictParams, TypedDictType, walk_typed_dict_type};
7373
pub use crate::types::variance::TypeVarVariance;
7474
use crate::types::variance::VarianceInferable;
@@ -5401,9 +5401,9 @@ impl<'db> Type<'db> {
54015401
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
54025402
bound.try_bool_impl(db, allow_short_circuit, visitor)?
54035403
}
5404-
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
5405-
try_union(constraints)?
5406-
}
5404+
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => constraints
5405+
.as_type(db)
5406+
.try_bool_impl(db, allow_short_circuit, visitor)?,
54075407
}
54085408
}
54095409

@@ -6453,7 +6453,7 @@ impl<'db> Type<'db> {
64536453
TypeVarBoundOrConstraints::UpperBound(bound) => {
64546454
non_async_special_case(db, bound)
64556455
}
6456-
TypeVarBoundOrConstraints::Constraints(union) => non_async_special_case(db, Type::Union(union)),
6456+
TypeVarBoundOrConstraints::Constraints(constraints) => non_async_special_case(db, constraints.as_type(db)),
64576457
},
64586458
Type::Union(union) => {
64596459
let elements = union.elements(db);
@@ -9594,7 +9594,7 @@ impl<'db> TypeVarInstance<'db> {
95949594
TypeVarBoundOrConstraints::UpperBound(upper_bound.to_instance(db)?)
95959595
}
95969596
TypeVarBoundOrConstraints::Constraints(constraints) => {
9597-
TypeVarBoundOrConstraints::Constraints(constraints.to_instance(db)?.as_union()?)
9597+
TypeVarBoundOrConstraints::Constraints(constraints.to_instance(db)?)
95989598
}
95999599
};
96009600
let identity = TypeVarIdentity::new(
@@ -9645,22 +9645,30 @@ impl<'db> TypeVarInstance<'db> {
96459645
fn lazy_constraints(self, db: &'db dyn Db) -> Option<TypeVarBoundOrConstraints<'db>> {
96469646
let definition = self.definition(db)?;
96479647
let module = parsed_module(db, definition.file(db)).load(db);
9648-
let ty = match definition.kind(db) {
9648+
let constraints = match definition.kind(db) {
96499649
// PEP 695 typevar
96509650
DefinitionKind::TypeVar(typevar) => {
96519651
let typevar_node = typevar.node(&module);
9652-
definition_expression_type(db, definition, typevar_node.bound.as_ref()?)
9653-
.as_union()?
9652+
let bound =
9653+
definition_expression_type(db, definition, typevar_node.bound.as_ref()?);
9654+
let constraints = if let Some(tuple) = bound
9655+
.as_nominal_instance()
9656+
.and_then(|instance| instance.tuple_spec(db))
9657+
{
9658+
if let Tuple::Fixed(tuple) = tuple.into_owned() {
9659+
tuple.owned_elements()
9660+
} else {
9661+
vec![Type::unknown()].into_boxed_slice()
9662+
}
9663+
} else {
9664+
vec![Type::unknown()].into_boxed_slice()
9665+
};
9666+
TypeVarConstraints::new(db, constraints)
96549667
}
96559668
// legacy typevar
96569669
DefinitionKind::Assignment(assignment) => {
96579670
let call_expr = assignment.value(&module).as_call_expr()?;
9658-
// We don't use `UnionType::from_elements` or `UnionBuilder` here,
9659-
// because we don't want to simplify the list of constraints as we would with
9660-
// an actual union type.
9661-
// TODO: We probably shouldn't use `UnionType` to store these at all? TypeVar
9662-
// constraints are not a union.
9663-
UnionType::new(
9671+
TypeVarConstraints::new(
96649672
db,
96659673
call_expr
96669674
.arguments
@@ -9669,12 +9677,11 @@ impl<'db> TypeVarInstance<'db> {
96699677
.skip(1)
96709678
.map(|arg| definition_expression_type(db, definition, arg))
96719679
.collect::<Box<_>>(),
9672-
RecursivelyDefined::No,
96739680
)
96749681
}
96759682
_ => return None,
96769683
};
9677-
Some(TypeVarBoundOrConstraints::Constraints(ty))
9684+
Some(TypeVarBoundOrConstraints::Constraints(constraints))
96789685
}
96799686

96809687
#[salsa::tracked(cycle_fn=lazy_default_cycle_recover, cycle_initial=lazy_default_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
@@ -10086,10 +10093,133 @@ impl<'db> From<TypeVarBoundOrConstraints<'db>> for TypeVarBoundOrConstraintsEval
1008610093
}
1008710094
}
1008810095

10096+
/// Type variable constraints (e.g. `T: (int, str)`).
10097+
/// This is structurally identical to [`UnionType`], except that it does not perform simplification and preserves the element types.
10098+
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
10099+
pub struct TypeVarConstraints<'db> {
10100+
#[returns(ref)]
10101+
elements: Box<[Type<'db>]>,
10102+
}
10103+
10104+
impl get_size2::GetSize for TypeVarConstraints<'_> {}
10105+
10106+
fn walk_type_var_constraints<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
10107+
db: &'db dyn Db,
10108+
constraints: TypeVarConstraints<'db>,
10109+
visitor: &V,
10110+
) {
10111+
for ty in constraints.elements(db) {
10112+
visitor.visit_type(db, *ty);
10113+
}
10114+
}
10115+
10116+
impl<'db> TypeVarConstraints<'db> {
10117+
fn as_type(self, db: &'db dyn Db) -> Type<'db> {
10118+
let mut builder = UnionBuilder::new(db);
10119+
for ty in self.elements(db) {
10120+
builder = builder.add(*ty);
10121+
}
10122+
builder.build()
10123+
}
10124+
10125+
fn to_instance(self, db: &'db dyn Db) -> Option<TypeVarConstraints<'db>> {
10126+
let mut instance_elements = Vec::new();
10127+
for ty in self.elements(db) {
10128+
instance_elements.push(ty.to_instance(db)?);
10129+
}
10130+
Some(TypeVarConstraints::new(
10131+
db,
10132+
instance_elements.into_boxed_slice(),
10133+
))
10134+
}
10135+
10136+
fn map(self, db: &'db dyn Db, transform_fn: impl FnMut(&Type<'db>) -> Type<'db>) -> Self {
10137+
let mapped = self
10138+
.elements(db)
10139+
.iter()
10140+
.map(transform_fn)
10141+
.collect::<Box<_>>();
10142+
TypeVarConstraints::new(db, mapped)
10143+
}
10144+
10145+
pub(crate) fn map_with_boundness_and_qualifiers(
10146+
self,
10147+
db: &'db dyn Db,
10148+
mut transform_fn: impl FnMut(&Type<'db>) -> PlaceAndQualifiers<'db>,
10149+
) -> PlaceAndQualifiers<'db> {
10150+
let mut builder = UnionBuilder::new(db);
10151+
let mut qualifiers = TypeQualifiers::empty();
10152+
10153+
let mut all_unbound = true;
10154+
let mut possibly_unbound = false;
10155+
let mut origin = TypeOrigin::Declared;
10156+
for ty in self.elements(db) {
10157+
let PlaceAndQualifiers {
10158+
place: ty_member,
10159+
qualifiers: new_qualifiers,
10160+
} = transform_fn(ty);
10161+
qualifiers |= new_qualifiers;
10162+
match ty_member {
10163+
Place::Undefined => {
10164+
possibly_unbound = true;
10165+
}
10166+
Place::Defined(ty_member, member_origin, member_boundness) => {
10167+
origin = origin.merge(member_origin);
10168+
if member_boundness == Definedness::PossiblyUndefined {
10169+
possibly_unbound = true;
10170+
}
10171+
10172+
all_unbound = false;
10173+
builder = builder.add(ty_member);
10174+
}
10175+
}
10176+
}
10177+
PlaceAndQualifiers {
10178+
place: if all_unbound {
10179+
Place::Undefined
10180+
} else {
10181+
Place::Defined(
10182+
builder.build(),
10183+
origin,
10184+
if possibly_unbound {
10185+
Definedness::PossiblyUndefined
10186+
} else {
10187+
Definedness::AlwaysDefined
10188+
},
10189+
)
10190+
},
10191+
qualifiers,
10192+
}
10193+
}
10194+
10195+
fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
10196+
let normalized = self
10197+
.elements(db)
10198+
.iter()
10199+
.map(|ty| ty.normalized_impl(db, visitor))
10200+
.collect::<Box<_>>();
10201+
TypeVarConstraints::new(db, normalized)
10202+
}
10203+
10204+
fn materialize_impl(
10205+
self,
10206+
db: &'db dyn Db,
10207+
materialization_kind: MaterializationKind,
10208+
visitor: &ApplyTypeMappingVisitor<'db>,
10209+
) -> Self {
10210+
let materialized = self
10211+
.elements(db)
10212+
.iter()
10213+
.map(|ty| ty.materialize(db, materialization_kind, visitor))
10214+
.collect::<Box<_>>();
10215+
TypeVarConstraints::new(db, materialized)
10216+
}
10217+
}
10218+
1008910219
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
1009010220
pub enum TypeVarBoundOrConstraints<'db> {
1009110221
UpperBound(Type<'db>),
10092-
Constraints(UnionType<'db>),
10222+
Constraints(TypeVarConstraints<'db>),
1009310223
}
1009410224

1009510225
fn walk_type_var_bounds<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
@@ -10100,7 +10230,7 @@ fn walk_type_var_bounds<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
1010010230
match bounds {
1010110231
TypeVarBoundOrConstraints::UpperBound(bound) => visitor.visit_type(db, bound),
1010210232
TypeVarBoundOrConstraints::Constraints(constraints) => {
10103-
visitor.visit_union_type(db, constraints);
10233+
walk_type_var_constraints(db, constraints, visitor);
1010410234
}
1010510235
}
1010610236
}
@@ -10112,18 +10242,7 @@ impl<'db> TypeVarBoundOrConstraints<'db> {
1011210242
TypeVarBoundOrConstraints::UpperBound(bound.normalized_impl(db, visitor))
1011310243
}
1011410244
TypeVarBoundOrConstraints::Constraints(constraints) => {
10115-
// Constraints are a non-normalized union by design (it's not really a union at
10116-
// all, we are just using a union to store the types). Normalize the types but not
10117-
// the containing union.
10118-
TypeVarBoundOrConstraints::Constraints(UnionType::new(
10119-
db,
10120-
constraints
10121-
.elements(db)
10122-
.iter()
10123-
.map(|ty| ty.normalized_impl(db, visitor))
10124-
.collect::<Box<_>>(),
10125-
constraints.recursively_defined(db),
10126-
))
10245+
TypeVarBoundOrConstraints::Constraints(constraints.normalized_impl(db, visitor))
1012710246
}
1012810247
}
1012910248
}
@@ -10147,14 +10266,13 @@ impl<'db> TypeVarBoundOrConstraints<'db> {
1014710266
// Normalize each constraint with its corresponding previous constraint
1014810267
let current_elements = constraints.elements(db);
1014910268
let prev_elements = prev_constraints.elements(db);
10150-
TypeVarBoundOrConstraints::Constraints(UnionType::new(
10269+
TypeVarBoundOrConstraints::Constraints(TypeVarConstraints::new(
1015110270
db,
1015210271
current_elements
1015310272
.iter()
1015410273
.zip(prev_elements.iter())
1015510274
.map(|(ty, prev_ty)| ty.cycle_normalized(db, *prev_ty, cycle))
1015610275
.collect::<Box<_>>(),
10157-
constraints.recursively_defined(db),
1015810276
))
1015910277
}
1016010278
// The choice of whether it's an upper bound or constraints is purely syntactic and
@@ -10175,15 +10293,9 @@ impl<'db> TypeVarBoundOrConstraints<'db> {
1017510293
TypeVarBoundOrConstraints::UpperBound(bound.recursive_type_normalized(db, cycle))
1017610294
}
1017710295
TypeVarBoundOrConstraints::Constraints(constraints) => {
10178-
TypeVarBoundOrConstraints::Constraints(UnionType::new(
10179-
db,
10180-
constraints
10181-
.elements(db)
10182-
.iter()
10183-
.map(|ty| ty.recursive_type_normalized(db, cycle))
10184-
.collect::<Box<_>>(),
10185-
constraints.recursively_defined(db),
10186-
))
10296+
TypeVarBoundOrConstraints::Constraints(
10297+
constraints.map(db, |ty| ty.recursive_type_normalized(db, cycle)),
10298+
)
1018710299
}
1018810300
}
1018910301
}
@@ -10199,14 +10311,10 @@ impl<'db> TypeVarBoundOrConstraints<'db> {
1019910311
bound.materialize(db, materialization_kind, visitor),
1020010312
),
1020110313
TypeVarBoundOrConstraints::Constraints(constraints) => {
10202-
TypeVarBoundOrConstraints::Constraints(UnionType::new(
10314+
TypeVarBoundOrConstraints::Constraints(constraints.materialize_impl(
1020310315
db,
10204-
constraints
10205-
.elements(db)
10206-
.iter()
10207-
.map(|ty| ty.materialize(db, materialization_kind, visitor))
10208-
.collect::<Box<_>>(),
10209-
RecursivelyDefined::No,
10316+
materialization_kind,
10317+
visitor,
1021010318
))
1021110319
}
1021210320
}

crates/ty_python_semantic/src/types/bound_super.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ impl<'db> BoundSuperError<'db> {
157157
.map(|c| c.display(db))
158158
.join(", ")
159159
));
160-
Type::Union(constraints)
160+
constraints.as_type(db)
161161
}
162162
None => {
163163
diagnostic.info(format_args!(
@@ -374,7 +374,7 @@ impl<'db> BoundSuperType<'db> {
374374
delegate_with_error_mapped(bound, Some(type_var))
375375
}
376376
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
377-
delegate_with_error_mapped(Type::Union(constraints), Some(type_var))
377+
delegate_with_error_mapped(constraints.as_type(db), Some(type_var))
378378
}
379379
None => delegate_with_error_mapped(Type::object(), Some(type_var)),
380380
};

crates/ty_python_semantic/src/types/builder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1255,7 +1255,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
12551255
speculative = speculative.add_positive(bound);
12561256
}
12571257
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
1258-
speculative = speculative.add_positive(Type::Union(constraints));
1258+
speculative = speculative.add_positive(constraints.as_type(db));
12591259
}
12601260
// TypeVars without a bound or constraint implicitly have `object` as their
12611261
// upper bound, and it is always a no-op to add `object` to an intersection.

crates/ty_python_semantic/src/types/infer/builder.rs

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ use crate::semantic_index::{
5050
ApplicableConstraints, EnclosingSnapshotResult, SemanticIndex, place_table,
5151
};
5252
use crate::subscript::{PyIndex, PySlice};
53-
use crate::types::builder::RecursivelyDefined;
5453
use crate::types::call::bind::{CallableDescription, MatchingOverloadIndex};
5554
use crate::types::call::{Binding, Bindings, CallArguments, CallError, CallErrorKind};
5655
use crate::types::class::{CodeGeneratorKind, FieldKind, MetaclassErrorKind, MethodDecorator};
@@ -3274,19 +3273,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
32743273
std::mem::replace(&mut self.deferred_state, DeferredExpressionState::Deferred);
32753274
match bound.as_deref() {
32763275
Some(expr @ ast::Expr::Tuple(ast::ExprTuple { elts, .. })) => {
3277-
// We don't use UnionType::from_elements or UnionBuilder here, because we don't
3278-
// want to simplify the list of constraints like we do with the elements of an
3279-
// actual union type.
3280-
// TODO: Consider using a new `OneOfType` connective here instead, since that
3281-
// more accurately represents the actual semantics of typevar constraints.
3282-
let ty = Type::Union(UnionType::new(
3276+
// Here, we interpret `bound` as a heterogeneous tuple and convert it to `TypeVarConstraints` in `TypeVarInstance::lazy_constraints`.
3277+
let tuple_ty = Type::heterogeneous_tuple(
32833278
self.db(),
32843279
elts.iter()
32853280
.map(|expr| self.infer_type_expression(expr))
32863281
.collect::<Box<[_]>>(),
3287-
RecursivelyDefined::No,
3288-
));
3289-
self.store_expression_type(expr, ty);
3282+
);
3283+
self.store_expression_type(expr, tuple_ty);
32903284
}
32913285
Some(expr) => {
32923286
self.infer_type_expression(expr);
@@ -11521,7 +11515,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
1152111515
if provided_type
1152211516
.when_assignable_to(
1152311517
db,
11524-
Type::Union(constraints),
11518+
constraints.as_type(db),
1152511519
InferableTypeVars::None,
1152611520
)
1152711521
.is_never_satisfied(db)

crates/ty_python_semantic/src/types/narrow.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ impl ClassInfoConstraintFunction {
198198
self.generate_constraint(db, bound)
199199
}
200200
TypeVarBoundOrConstraints::Constraints(constraints) => {
201-
self.generate_constraint(db, Type::Union(constraints))
201+
self.generate_constraint(db, constraints.as_type(db))
202202
}
203203
}
204204
}

0 commit comments

Comments
 (0)