Skip to content

Conversation

@dcreager
Copy link
Member

This was a lingering place where we were still using TypeVarInstance equality to check whether two typevars are the same. We need to fall back on comparing the typevar identity, since that remains the same even after evaluating lazy defaults, bounds, or constraints.

This was showing up as a bug on #20933, since the new constraint solver normalizes types as part of constructing constraint sets. That had the effect of making a TypeVar instance fail the argument/parameter assignability check after we apply the inferred specialization in something like

def f[T = int]():
    reveal_type(T)

since the argument type has a lazy default, but the normalized/specialized parameter type ends up with the evaluated/eager default.

@dcreager dcreager added the internal An internal refactor or improvement label Nov 18, 2025
@dcreager dcreager requested a review from carljm as a code owner November 18, 2025 02:59
@dcreager dcreager added the ty Multi-file analysis & type inference label Nov 18, 2025
@astral-sh-bot
Copy link

astral-sh-bot bot commented Nov 18, 2025

Diagnostic diff on typing conformance tests

No changes detected when running ty on typing conformance tests ✅

@astral-sh-bot
Copy link

astral-sh-bot bot commented Nov 18, 2025

mypy_primer results

No ecosystem changes detected ✅

No memory usage changes detected ✅

Comment on lines +8128 to +8160
fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
match (self, other) {
(Self::TypeVar(self_typevar), Self::TypeVar(other_typevar)) => {
self_typevar.is_same_typevar_as(db, other_typevar)
}
(
Self::SubscriptedProtocol(_)
| Self::SubscriptedGeneric(_)
| Self::TypeVar(_)
| Self::TypeAliasType(_)
| Self::Deprecated(_)
| Self::Field(_)
| Self::ConstraintSet(_)
| Self::UnionType(_)
| Self::Literal(_)
| Self::Annotated(_)
| Self::TypeGenericAlias(_)
| Self::NewType(_),
Self::SubscriptedProtocol(_)
| Self::SubscriptedGeneric(_)
| Self::TypeVar(_)
| Self::TypeAliasType(_)
| Self::Deprecated(_)
| Self::Field(_)
| Self::ConstraintSet(_)
| Self::UnionType(_)
| Self::Literal(_)
| Self::Annotated(_)
| Self::TypeGenericAlias(_)
| Self::NewType(_),
) => self == other,
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC, all of these KnownInstance type variants essentially represent the type of a parameterized special form in a value position. so for T = TypeVar("T"), we represent the type of T as being a Type::KnownInstanceType(KnownInstanceType::TypeVar(..)). Given this, why is it important to check two typevars for equivalence using .is_same_typevar_as(), but not do the same for other special forms in value positions (which might be parameterized with typevars). E.g. when comparing whether T | int is equivalent to T | int, where T is a type variable -- here T | int will be represented in our model as a Type::KnownInstanceType(KnownInstanceType::UnionType(..)), where one of the elements in that union is a Type::KnownInstanceType(KnownInstanceType::TypeVar(..)): ISTM we need to account for the possibility of Type::KnownInstanceType(KnownInstanceType::TypeVar(..))s being in nested positions here, which will require recursing into the types rather than just using == checks

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm yes I think you're right!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

internal An internal refactor or improvement ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants