From 2d301b63bb15f6fb09bace24acfd8f42e69a234d Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Fri, 21 Nov 2025 08:44:30 -0500 Subject: [PATCH] test fixes from codex --- crates/ty_python_semantic/src/types.rs | 72 ++++++++++++------- .../ty_python_semantic/src/types/generics.rs | 33 +++++++++ 2 files changed, 81 insertions(+), 24 deletions(-) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 9424fbb72b..1b34f18064 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1756,6 +1756,24 @@ impl<'db> Type<'db> { } match (self, target) { + // Pretend that instances of `dataclasses.Field` are assignable to their default type. + // This allows field definitions like `name: str = field(default="")` in dataclasses + // to pass the assignability check of the inferred type to the declared type. + (Type::KnownInstance(KnownInstanceType::Field(field)), right) + if relation.is_assignability() => + { + field.default_type(db).when_none_or(|default_type| { + default_type.has_relation_to_impl( + db, + right, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + }) + } + // Two identical typevars must always solve to the same type, so they are always // subtypes of each other and assignable to each other. // @@ -1766,6 +1784,30 @@ impl<'db> Type<'db> { { ConstraintSet::from(true) } + (Type::TypeVar(lhs_bound_typevar), Type::TypeVar(rhs_bound_typevar)) + if relation.is_assignability() + && lhs_bound_typevar.typevar(db).identity(db) + == rhs_bound_typevar.typevar(db).identity(db) => + { + ConstraintSet::from(true) + } + + // Typevars with non-fully-static bounds or constraints do not participate in + // subtyping while they are in a non-inferable position. + (Type::TypeVar(bound_typevar), _) + if relation.is_subtyping() + && !bound_typevar.is_inferable(db, inferable) + && !bound_typevar.has_fully_static_bound_or_constraints(db) => + { + ConstraintSet::from(false) + } + (_, Type::TypeVar(bound_typevar)) + if relation.is_subtyping() + && !bound_typevar.is_inferable(db, inferable) + && !bound_typevar.has_fully_static_bound_or_constraints(db) => + { + ConstraintSet::from(false) + } // A typevar satisfies a relation when...it satisfies the relation. Yes that's a // tautology! We're moving the caller's subtyping/assignability requirement into a @@ -1825,24 +1867,6 @@ impl<'db> Type<'db> { }) } - // Pretend that instances of `dataclasses.Field` are assignable to their default type. - // This allows field definitions like `name: str = field(default="")` in dataclasses - // to pass the assignability check of the inferred type to the declared type. - (Type::KnownInstance(KnownInstanceType::Field(field)), right) - if relation.is_assignability() => - { - field.default_type(db).when_none_or(|default_type| { - default_type.has_relation_to_impl( - db, - right, - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - }) - } - // Dynamic is only a subtype of `object` and only a supertype of `Never`; both were // handled above. It's always assignable, though. // @@ -1991,12 +2015,12 @@ impl<'db> Type<'db> { self_function.has_relation_to_impl( db, target_function, - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - } + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + } (Type::BoundMethod(self_method), Type::BoundMethod(target_method)) => self_method .has_relation_to_impl( db, diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 40f36970fa..3e1f817b62 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -142,6 +142,25 @@ impl<'db> BoundTypeVarInstance<'db> { } } } + + /// Returns `true` if all bounds or constraints on this typevar are fully static. + /// + /// A type is fully static if its top and bottom materializations are the same; dynamic types + /// (like `Any`) will have different materializations. + pub(crate) fn has_fully_static_bound_or_constraints(self, db: &'db dyn Db) -> bool { + match self.typevar(db).bound_or_constraints(db) { + None => true, + Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { + bound.top_materialization(db) == bound.bottom_materialization(db) + } + Some(TypeVarBoundOrConstraints::Constraints(constraints)) => constraints + .elements(db) + .iter() + .all(|constraint| { + constraint.top_materialization(db) == constraint.bottom_materialization(db) + }), + } + } } impl<'a, 'db> InferableTypeVars<'a, 'db> { @@ -1521,6 +1540,20 @@ impl<'db> SpecializationBuilder<'db> { let bound_typevars = (formal.elements(self.db).iter()).filter_map(|ty| ty.as_typevar()); if let Ok(bound_typevar) = bound_typevars.exactly_one() { + // If the actual argument can be satisfied by a non-typevar element of the + // union, then prefer that element and avoid inferring a mapping for the + // typevar. This lets other occurrences of the typevar (if any) drive the + // specialization instead of opportunistically binding it here. + let matches_non_typevar = formal.elements(self.db).iter().any(|ty| { + !ty.has_typevar(self.db) + && actual + .when_subtype_of(self.db, *ty, self.inferable) + .satisfied_by_all_typevars(self.db, self.inferable) + }); + if matches_non_typevar { + return Ok(()); + } + self.add_type_mapping(bound_typevar, actual, polarity, f); } }