Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
77a62ae
return constraint sets directly in function comparison
dcreager Oct 1, 2025
296d485
pass inferable typevars explicitly
dcreager Oct 1, 2025
c7b9270
it's a start
dcreager Oct 1, 2025
f5720c4
separate type for inferable
dcreager Oct 1, 2025
9883c12
track inferable when infering
dcreager Oct 1, 2025
4ac1f29
function signature typevars are inferable
dcreager Oct 2, 2025
5ddfc1d
clippy
dcreager Oct 2, 2025
7d07b84
add display
dcreager Oct 2, 2025
720c704
track inferable during calls better
dcreager Oct 2, 2025
4a3e94e
use inferable here too
dcreager Oct 2, 2025
d890b41
more inferable
dcreager Oct 2, 2025
20f394c
fix incorrect xarray ecosystem results
dcreager Oct 3, 2025
c76ba5d
track heap size
dcreager Oct 3, 2025
88c388b
fix fuzz panics
dcreager Oct 3, 2025
5db9be8
absolutely consistent
dcreager Oct 7, 2025
53689ef
only infer inferable typevars
dcreager Oct 7, 2025
30f322c
include inferable in snapshot
dcreager Oct 7, 2025
e959369
skip bidi for parameters with typevars
dcreager Oct 10, 2025
998fcae
clippy!
dcreager Oct 10, 2025
5919ef2
Implement TypeVar identity refactoring
dcreager Oct 11, 2025
7cc7faa
clean up claudisms
dcreager Oct 11, 2025
0644e02
Refactor GenericContext to use GenericContextTypeVar struct
dcreager Oct 11, 2025
782214a
var -> variable
dcreager Oct 11, 2025
2ca6c1e
Remove unused 'original' field from TypeVarInstance
dcreager Oct 11, 2025
e6645bb
superfluous
dcreager Oct 11, 2025
6dab7e7
precommit
dcreager Oct 11, 2025
a4dc171
remove finished plan
dcreager Oct 11, 2025
e2f5b50
Merge branch 'dcreager/typevar-identity' into dcreager/non-non-inferable
dcreager Oct 11, 2025
625cee6
pre-commit
dcreager Oct 11, 2025
bbacf20
Move display method to BoundTypeVarIdentity
dcreager Oct 11, 2025
119bcb4
clippy
dcreager Oct 11, 2025
9adc068
Update ConstraintSet to use BoundTypeVarIdentity
dcreager Oct 11, 2025
7dc5144
clippy
dcreager Oct 11, 2025
f589ad0
pre-commit
dcreager Oct 11, 2025
ba69b80
Merge branch 'dcreager/typevar-identity' into dcreager/non-non-inferable
dcreager Oct 11, 2025
5d2738e
Merge branch 'main' into dcreager/typevar-identity
dcreager Oct 11, 2025
0392d9f
Merge branch 'dcreager/typevar-identity' into dcreager/non-non-inferable
dcreager Oct 11, 2025
88f3a21
merge conflicts
dcreager Oct 11, 2025
dd9abdf
missed one
dcreager Oct 11, 2025
8caf208
fix display
dcreager Oct 11, 2025
69a2b33
inferable in filter_disjoint
dcreager Oct 11, 2025
b2825f0
fix tests
dcreager Oct 11, 2025
375b9a3
Merge branch 'dcreager/typevar-identity' into dcreager/non-non-inferable
dcreager Oct 11, 2025
c836146
Merge remote-tracking branch 'origin/main' into dcreager/non-non-infe…
dcreager Oct 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/ty_ide/src/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ impl<'db> Completion<'db> {
| Type::BytesLiteral(_) => CompletionKind::Value,
Type::EnumLiteral(_) => CompletionKind::Enum,
Type::ProtocolInstance(_) => CompletionKind::Interface,
Type::NonInferableTypeVar(_) | Type::TypeVar(_) => CompletionKind::TypeParameter,
Type::TypeVar(_) => CompletionKind::TypeParameter,
Type::Union(union) => union
.elements(db)
.iter()
Expand Down
4 changes: 1 addition & 3 deletions crates/ty_ide/src/semantic_tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,9 +336,7 @@ impl<'db> SemanticTokenVisitor<'db> {

match ty {
Type::ClassLiteral(_) => (SemanticTokenType::Class, modifiers),
Type::NonInferableTypeVar(_) | Type::TypeVar(_) => {
(SemanticTokenType::TypeParameter, modifiers)
}
Type::TypeVar(_) => (SemanticTokenType::TypeParameter, modifiers),
Type::FunctionLiteral(_) => {
// Check if this is a method based on current scope
if self.in_class_scope {
Expand Down
739 changes: 412 additions & 327 deletions crates/ty_python_semantic/src/types.rs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion crates/ty_python_semantic/src/types/bound_super.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ impl<'db> BoundSuperType<'db> {
Type::TypeAlias(alias) => {
return delegate_with_error_mapped(alias.value_type(db), None);
}
Type::TypeVar(type_var) | Type::NonInferableTypeVar(type_var) => {
Type::TypeVar(type_var) => {
let type_var = type_var.typevar(db);
return match type_var.bound_or_constraints(db) {
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
Expand Down
3 changes: 1 addition & 2 deletions crates/ty_python_semantic/src/types/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1062,8 +1062,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
let mut positive_to_remove = SmallVec::<[usize; 1]>::new();

for (typevar_index, ty) in self.positive.iter().enumerate() {
let (Type::NonInferableTypeVar(bound_typevar) | Type::TypeVar(bound_typevar)) = ty
else {
let Type::TypeVar(bound_typevar) = ty else {
continue;
};
let Some(TypeVarBoundOrConstraints::Constraints(constraints)) =
Expand Down
78 changes: 58 additions & 20 deletions crates/ty_python_semantic/src/types/call/bind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ use crate::types::enums::is_enum_class;
use crate::types::function::{
DataclassTransformerParams, FunctionDecorators, FunctionType, KnownFunction, OverloadLiteral,
};
use crate::types::generics::{Specialization, SpecializationBuilder, SpecializationError};
use crate::types::generics::{
InferableTypeVars, Specialization, SpecializationBuilder, SpecializationError,
};
use crate::types::signatures::{Parameter, ParameterForm, ParameterKind, Parameters};
use crate::types::tuple::{TupleLength, TupleType};
use crate::types::{
Expand Down Expand Up @@ -597,7 +599,8 @@ impl<'db> Bindings<'db> {
Type::FunctionLiteral(function_type) => match function_type.known(db) {
Some(KnownFunction::IsEquivalentTo) => {
if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() {
let constraints = ty_a.when_equivalent_to(db, *ty_b);
let constraints =
ty_a.when_equivalent_to(db, *ty_b, InferableTypeVars::None);
let tracked = TrackedConstraintSet::new(db, constraints);
overload.set_return_type(Type::KnownInstance(
KnownInstanceType::ConstraintSet(tracked),
Expand All @@ -607,7 +610,8 @@ impl<'db> Bindings<'db> {

Some(KnownFunction::IsSubtypeOf) => {
if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() {
let constraints = ty_a.when_subtype_of(db, *ty_b);
let constraints =
ty_a.when_subtype_of(db, *ty_b, InferableTypeVars::None);
let tracked = TrackedConstraintSet::new(db, constraints);
overload.set_return_type(Type::KnownInstance(
KnownInstanceType::ConstraintSet(tracked),
Expand All @@ -617,7 +621,8 @@ impl<'db> Bindings<'db> {

Some(KnownFunction::IsAssignableTo) => {
if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() {
let constraints = ty_a.when_assignable_to(db, *ty_b);
let constraints =
ty_a.when_assignable_to(db, *ty_b, InferableTypeVars::None);
let tracked = TrackedConstraintSet::new(db, constraints);
overload.set_return_type(Type::KnownInstance(
KnownInstanceType::ConstraintSet(tracked),
Expand All @@ -627,7 +632,8 @@ impl<'db> Bindings<'db> {

Some(KnownFunction::IsDisjointFrom) => {
if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() {
let constraints = ty_a.when_disjoint_from(db, *ty_b);
let constraints =
ty_a.when_disjoint_from(db, *ty_b, InferableTypeVars::None);
let tracked = TrackedConstraintSet::new(db, constraints);
overload.set_return_type(Type::KnownInstance(
KnownInstanceType::ConstraintSet(tracked),
Expand Down Expand Up @@ -1407,7 +1413,10 @@ impl<'db> CallableBinding<'db> {
let parameter_type = overload.signature.parameters()[*parameter_index]
.annotated_type()
.unwrap_or(Type::unknown());
if argument_type.is_assignable_to(db, parameter_type) {
if argument_type
.when_assignable_to(db, parameter_type, overload.inferable_typevars)
.is_always_satisfied()
{
is_argument_assignable_to_any_overload = true;
break 'overload;
}
Expand Down Expand Up @@ -1633,7 +1642,14 @@ impl<'db> CallableBinding<'db> {
.unwrap_or(Type::unknown());
let first_parameter_type = &mut first_parameter_types[parameter_index];
if let Some(first_parameter_type) = first_parameter_type {
if !first_parameter_type.is_equivalent_to(db, current_parameter_type) {
if !first_parameter_type
.when_equivalent_to(
db,
current_parameter_type,
overload.inferable_typevars,
)
.is_always_satisfied()
{
participating_parameter_indexes.insert(parameter_index);
}
} else {
Expand Down Expand Up @@ -1750,7 +1766,12 @@ impl<'db> CallableBinding<'db> {
matching_overloads.all(|(_, overload)| {
overload
.return_type()
.is_equivalent_to(db, first_overload_return_type)
.when_equivalent_to(
db,
first_overload_return_type,
overload.inferable_typevars,
)
.is_always_satisfied()
})
} else {
// No matching overload
Expand Down Expand Up @@ -2461,6 +2482,7 @@ struct ArgumentTypeChecker<'a, 'db> {
call_expression_tcx: &'a TypeContext<'db>,
errors: &'a mut Vec<BindingError<'db>>,

inferable_typevars: InferableTypeVars<'db, 'db>,
specialization: Option<Specialization<'db>>,
}

Expand All @@ -2482,6 +2504,7 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
parameter_tys,
call_expression_tcx,
errors,
inferable_typevars: InferableTypeVars::None,
specialization: None,
}
}
Expand Down Expand Up @@ -2514,11 +2537,12 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
}

fn infer_specialization(&mut self) {
if self.signature.generic_context.is_none() {
let Some(generic_context) = self.signature.generic_context else {
return;
}
};

let mut builder = SpecializationBuilder::new(self.db);
self.inferable_typevars = generic_context.inferable_typevars(self.db);
let mut builder = SpecializationBuilder::new(self.db, self.inferable_typevars);

// Note that we infer the annotated type _before_ the arguments if this call is part of
// an annotated assignment, to closer match the order of any unions written in the type
Expand Down Expand Up @@ -2563,10 +2587,7 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
}
}

self.specialization = self
.signature
.generic_context
.map(|gc| builder.build(gc, *self.call_expression_tcx));
self.specialization = Some(builder.build(generic_context, *self.call_expression_tcx));
}

fn check_argument_type(
Expand All @@ -2590,7 +2611,7 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
// constraint set that we get from this assignability check, instead of inferring and
// building them in an earlier separate step.
if argument_type
.when_assignable_to(self.db, expected_ty)
.when_assignable_to(self.db, expected_ty, self.inferable_typevars)
.is_never_satisfied()
{
let positional = matches!(argument, Argument::Positional | Argument::Synthetic)
Expand Down Expand Up @@ -2719,7 +2740,14 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
return;
};

if !key_type.is_assignable_to(self.db, KnownClass::Str.to_instance(self.db)) {
if !key_type
.when_assignable_to(
self.db,
KnownClass::Str.to_instance(self.db),
self.inferable_typevars,
)
.is_always_satisfied()
{
self.errors.push(BindingError::InvalidKeyType {
argument_index: adjusted_argument_index,
provided_ty: key_type,
Expand Down Expand Up @@ -2754,8 +2782,8 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
}
}

fn finish(self) -> Option<Specialization<'db>> {
self.specialization
fn finish(self) -> (InferableTypeVars<'db, 'db>, Option<Specialization<'db>>) {
(self.inferable_typevars, self.specialization)
}
}

Expand Down Expand Up @@ -2819,6 +2847,9 @@ pub(crate) struct Binding<'db> {
/// Return type of the call.
return_ty: Type<'db>,

/// The inferable typevars in this signature.
inferable_typevars: InferableTypeVars<'db, 'db>,

/// The specialization that was inferred from the argument types, if the callable is generic.
specialization: Option<Specialization<'db>>,

Expand All @@ -2845,6 +2876,7 @@ impl<'db> Binding<'db> {
callable_type: signature_type,
signature_type,
return_ty: Type::unknown(),
inferable_typevars: InferableTypeVars::None,
specialization: None,
argument_matches: Box::from([]),
variadic_argument_matched_to_variadic_parameter: false,
Expand Down Expand Up @@ -2916,7 +2948,7 @@ impl<'db> Binding<'db> {
checker.infer_specialization();

checker.check_argument_types();
self.specialization = checker.finish();
(self.inferable_typevars, self.specialization) = checker.finish();
if let Some(specialization) = self.specialization {
self.return_ty = self.return_ty.apply_specialization(db, specialization);
}
Expand Down Expand Up @@ -3010,6 +3042,7 @@ impl<'db> Binding<'db> {
fn snapshot(&self) -> BindingSnapshot<'db> {
BindingSnapshot {
return_ty: self.return_ty,
inferable_typevars: self.inferable_typevars,
specialization: self.specialization,
argument_matches: self.argument_matches.clone(),
parameter_tys: self.parameter_tys.clone(),
Expand All @@ -3020,13 +3053,15 @@ impl<'db> Binding<'db> {
fn restore(&mut self, snapshot: BindingSnapshot<'db>) {
let BindingSnapshot {
return_ty,
inferable_typevars,
specialization,
argument_matches,
parameter_tys,
errors,
} = snapshot;

self.return_ty = return_ty;
self.inferable_typevars = inferable_typevars;
self.specialization = specialization;
self.argument_matches = argument_matches;
self.parameter_tys = parameter_tys;
Expand All @@ -3046,6 +3081,7 @@ impl<'db> Binding<'db> {
/// Resets the state of this binding to its initial state.
fn reset(&mut self) {
self.return_ty = Type::unknown();
self.inferable_typevars = InferableTypeVars::None;
self.specialization = None;
self.argument_matches = Box::from([]);
self.parameter_tys = Box::from([]);
Expand All @@ -3056,6 +3092,7 @@ impl<'db> Binding<'db> {
#[derive(Clone, Debug)]
struct BindingSnapshot<'db> {
return_ty: Type<'db>,
inferable_typevars: InferableTypeVars<'db, 'db>,
specialization: Option<Specialization<'db>>,
argument_matches: Box<[MatchedArgument<'db>]>,
parameter_tys: Box<[Option<Type<'db>>]>,
Expand Down Expand Up @@ -3095,6 +3132,7 @@ impl<'db> CallableBindingSnapshot<'db> {

// ... and update the snapshot with the current state of the binding.
snapshot.return_ty = binding.return_ty;
snapshot.inferable_typevars = binding.inferable_typevars;
snapshot.specialization = binding.specialization;
snapshot
.argument_matches
Expand Down
22 changes: 12 additions & 10 deletions crates/ty_python_semantic/src/types/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use crate::types::diagnostic::INVALID_TYPE_ALIAS_TYPE;
use crate::types::enums::enum_metadata;
use crate::types::function::{DataclassTransformerParams, KnownFunction};
use crate::types::generics::{
GenericContext, Specialization, walk_generic_context, walk_specialization,
GenericContext, InferableTypeVars, Specialization, walk_generic_context, walk_specialization,
};
use crate::types::infer::nearest_enclosing_class;
use crate::types::member::{Member, class_member};
Expand Down Expand Up @@ -540,17 +540,20 @@ impl<'db> ClassType<'db> {

/// Return `true` if `other` is present in this class's MRO.
pub(super) fn is_subclass_of(self, db: &'db dyn Db, other: ClassType<'db>) -> bool {
self.when_subclass_of(db, other).is_always_satisfied()
self.when_subclass_of(db, other, InferableTypeVars::None)
.is_always_satisfied()
}

pub(super) fn when_subclass_of(
self,
db: &'db dyn Db,
other: ClassType<'db>,
inferable: InferableTypeVars<'_, 'db>,
) -> ConstraintSet<'db> {
self.has_relation_to_impl(
db,
other,
inferable,
TypeRelation::Subtyping,
&HasRelationToVisitor::default(),
&IsDisjointVisitor::default(),
Expand All @@ -561,6 +564,7 @@ impl<'db> ClassType<'db> {
self,
db: &'db dyn Db,
other: Self,
inferable: InferableTypeVars<'_, 'db>,
relation: TypeRelation,
relation_visitor: &HasRelationToVisitor<'db>,
disjointness_visitor: &IsDisjointVisitor<'db>,
Expand All @@ -586,6 +590,7 @@ impl<'db> ClassType<'db> {
base.specialization(db).has_relation_to_impl(
db,
other.specialization(db),
inferable,
relation,
relation_visitor,
disjointness_visitor,
Expand All @@ -610,6 +615,7 @@ impl<'db> ClassType<'db> {
self,
db: &'db dyn Db,
other: ClassType<'db>,
inferable: InferableTypeVars<'_, 'db>,
visitor: &IsEquivalentVisitor<'db>,
) -> ConstraintSet<'db> {
if self == other {
Expand All @@ -628,6 +634,7 @@ impl<'db> ClassType<'db> {
this.specialization(db).is_equivalent_to_impl(
db,
other.specialization(db),
inferable,
visitor,
)
})
Expand Down Expand Up @@ -1617,15 +1624,10 @@ impl<'db> ClassLiteral<'db> {
})
}

/// Returns a specialization of this class where each typevar is mapped to itself. The second
/// parameter can be `Type::TypeVar` or `Type::NonInferableTypeVar`, depending on the use case.
pub(crate) fn identity_specialization(
self,
db: &'db dyn Db,
typevar_to_type: &impl Fn(BoundTypeVarInstance<'db>) -> Type<'db>,
) -> ClassType<'db> {
/// Returns a specialization of this class where each typevar is mapped to itself.
pub(crate) fn identity_specialization(self, db: &'db dyn Db) -> ClassType<'db> {
self.apply_specialization(db, |generic_context| {
generic_context.identity_specialization(db, typevar_to_type)
generic_context.identity_specialization(db)
})
}

Expand Down
1 change: 0 additions & 1 deletion crates/ty_python_semantic/src/types/class_base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,6 @@ impl<'db> ClassBase<'db> {
| Type::StringLiteral(_)
| Type::LiteralString
| Type::ModuleLiteral(_)
| Type::NonInferableTypeVar(_)
| Type::TypeVar(_)
| Type::BoundSuper(_)
| Type::ProtocolInstance(_)
Expand Down
4 changes: 1 addition & 3 deletions crates/ty_python_semantic/src/types/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -560,9 +560,7 @@ impl Display for DisplayRepresentation<'_> {
.display_with(self.db, self.settings.clone()),
literal_name = enum_literal.name(self.db)
),
Type::NonInferableTypeVar(bound_typevar) | Type::TypeVar(bound_typevar) => {
bound_typevar.identity(self.db).display(self.db).fmt(f)
}
Type::TypeVar(bound_typevar) => bound_typevar.identity(self.db).display(self.db).fmt(f),
Type::AlwaysTruthy => f.write_str("AlwaysTruthy"),
Type::AlwaysFalsy => f.write_str("AlwaysFalsy"),
Type::BoundSuper(bound_super) => {
Expand Down
Loading
Loading