diff --git a/crates/ty_ide/src/completion.rs b/crates/ty_ide/src/completion.rs index a3bae6793f..c5b7c2a358 100644 --- a/crates/ty_ide/src/completion.rs +++ b/crates/ty_ide/src/completion.rs @@ -1247,7 +1247,7 @@ quux. __init_subclass__ :: bound method Quux.__init_subclass__() -> None __module__ :: str __ne__ :: bound method Quux.__ne__(value: object, /) -> bool - __new__ :: bound method Quux.__new__() -> Self@object + __new__ :: bound method Quux.__new__() -> Self@__new__ __reduce__ :: bound method Quux.__reduce__() -> str | tuple[Any, ...] __reduce_ex__ :: bound method Quux.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...] __repr__ :: bound method Quux.__repr__() -> str @@ -1292,7 +1292,7 @@ quux.b __init_subclass__ :: bound method Quux.__init_subclass__() -> None __module__ :: str __ne__ :: bound method Quux.__ne__(value: object, /) -> bool - __new__ :: bound method Quux.__new__() -> Self@object + __new__ :: bound method Quux.__new__() -> Self@__new__ __reduce__ :: bound method Quux.__reduce__() -> str | tuple[Any, ...] __reduce_ex__ :: bound method Quux.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...] __repr__ :: bound method Quux.__repr__() -> str @@ -1346,7 +1346,7 @@ C. __mro__ :: tuple[, ] __name__ :: str __ne__ :: def __ne__(self, value: object, /) -> bool - __new__ :: def __new__(cls) -> Self@object + __new__ :: def __new__(cls) -> Self@__new__ __or__ :: bound method .__or__(value: Any, /) -> UnionType __prepare__ :: bound method .__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object] __qualname__ :: str @@ -1522,7 +1522,7 @@ Quux. __mro__ :: tuple[, ] __name__ :: str __ne__ :: def __ne__(self, value: object, /) -> bool - __new__ :: def __new__(cls) -> Self@object + __new__ :: def __new__(cls) -> Self@__new__ __or__ :: bound method .__or__(value: Any, /) -> UnionType __prepare__ :: bound method .__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object] __qualname__ :: str @@ -1574,8 +1574,8 @@ Answer. __bool__ :: bound method .__bool__() -> Literal[True] __class__ :: __contains__ :: bound method .__contains__(value: object) -> bool - __copy__ :: def __copy__(self) -> Self@Enum - __deepcopy__ :: def __deepcopy__(self, memo: Any) -> Self@Enum + __copy__ :: def __copy__(self) -> Self@__copy__ + __deepcopy__ :: def __deepcopy__(self, memo: Any) -> Self@__deepcopy__ __delattr__ :: def __delattr__(self, name: str, /) -> None __dict__ :: MappingProxyType[str, Any] __dictoffset__ :: int @@ -1599,7 +1599,7 @@ Answer. __mro__ :: tuple[, , ] __name__ :: str __ne__ :: def __ne__(self, value: object, /) -> bool - __new__ :: def __new__(cls, value: object) -> Self@Enum + __new__ :: def __new__(cls, value: object) -> Self@__new__ __or__ :: bound method .__or__(value: Any, /) -> UnionType __order__ :: str __prepare__ :: bound method .__prepare__(cls: str, bases: tuple[type, ...], **kwds: Any) -> _EnumDict diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/self.md b/crates/ty_python_semantic/resources/mdtest/annotations/self.md index d74f67c430..d03dd21c02 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/self.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/self.md @@ -16,7 +16,7 @@ from typing import Self class Shape: def set_scale(self: Self, scale: float) -> Self: - reveal_type(self) # revealed: Self@Shape + reveal_type(self) # revealed: Self@set_scale return self def nested_type(self: Self) -> list[Self]: @@ -24,10 +24,17 @@ class Shape: def nested_func(self: Self) -> Self: def inner() -> Self: - reveal_type(self) # revealed: Self@Shape + reveal_type(self) # revealed: Self@nested_func return self return inner() + def nested_func_without_enclosing_binding(self): + def inner(x: Self): + # TODO: revealed: Self@nested_func_without_enclosing_binding + # (The outer method binds an implicit `Self`) + reveal_type(x) # revealed: Self@inner + inner(self) + def implicit_self(self) -> Self: # TODO: first argument in a method should be considered as "typing.Self" reveal_type(self) # revealed: Unknown @@ -38,13 +45,13 @@ reveal_type(Shape().nested_func()) # revealed: Shape class Circle(Shape): def set_scale(self: Self, scale: float) -> Self: - reveal_type(self) # revealed: Self@Circle + reveal_type(self) # revealed: Self@set_scale return self class Outer: class Inner: def foo(self: Self) -> Self: - reveal_type(self) # revealed: Self@Inner + reveal_type(self) # revealed: Self@foo return self ``` @@ -99,6 +106,9 @@ reveal_type(Shape.bar()) # revealed: Unknown python-version = "3.11" ``` +TODO: The use of `Self` to annotate the `next_node` attribute should be +[modeled as a property][self attribute], using `Self` in its parameter and return type. + ```py from typing import Self @@ -108,6 +118,8 @@ class LinkedList: def next(self: Self) -> Self: reveal_type(self.value) # revealed: int + # TODO: no error + # error: [invalid-return-type] return self.next_node reveal_type(LinkedList().next()) # revealed: LinkedList @@ -151,7 +163,7 @@ from typing import Self class Shape: def union(self: Self, other: Self | None): - reveal_type(other) # revealed: Self@Shape | None + reveal_type(other) # revealed: Self@union | None return self ``` @@ -205,3 +217,5 @@ class MyMetaclass(type): def __new__(cls) -> Self: return super().__new__(cls) ``` + +[self attribute]: https://typing.python.org/en/latest/spec/generics.html#use-in-attribute-annotations diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md b/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md index 60d9826628..3b10945df9 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md @@ -27,7 +27,7 @@ def i(callback: Callable[Concatenate[int, P], R_co], *args: P.args, **kwargs: P. class Foo: def method(self, x: Self): - reveal_type(x) # revealed: Self@Foo + reveal_type(x) # revealed: Self@method ``` ## Type expressions diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md index 5526e77782..a2fdc631ae 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md @@ -365,3 +365,33 @@ def g(x: T) -> T | None: reveal_type(f(g("a"))) # revealed: tuple[Literal["a"] | None, int] reveal_type(g(f("a"))) # revealed: tuple[Literal["a"], int] | None ``` + +## Opaque decorators don't affect typevar binding + +Inside the body of a generic function, we should be able to see that the typevars bound by that +function are in fact bound by that function. This requires being able to see the enclosing +function's _undecorated_ type and signature, especially in the case where a gradually typed +decorator "hides" the function type from outside callers. + +```py +from typing import cast, Any, Callable, TypeVar + +F = TypeVar("F", bound=Callable[..., Any]) +T = TypeVar("T") + +def opaque_decorator(f: Any) -> Any: + return f + +def transparent_decorator(f: F) -> F: + return f + +@opaque_decorator +def decorated(t: T) -> None: + # error: [redundant-cast] + reveal_type(cast(T, t)) # revealed: T@decorated + +@transparent_decorator +def decorated(t: T) -> None: + # error: [redundant-cast] + reveal_type(cast(T, t)) # revealed: T@decorated +``` diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md index fa08ca1da5..c42bd17f86 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md @@ -377,3 +377,30 @@ def f( # error: [invalid-argument-type] "does not satisfy upper bound" reveal_type(close_and_return(g)) # revealed: Unknown ``` + +## Opaque decorators don't affect typevar binding + +Inside the body of a generic function, we should be able to see that the typevars bound by that +function are in fact bound by that function. This requires being able to see the enclosing +function's _undecorated_ type and signature, especially in the case where a gradually typed +decorator "hides" the function type from outside callers. + +```py +from typing import cast, Any, Callable + +def opaque_decorator(f: Any) -> Any: + return f + +def transparent_decorator[F: Callable[..., Any]](f: F) -> F: + return f + +@opaque_decorator +def decorated[T](t: T) -> None: + # error: [redundant-cast] + reveal_type(cast(T, t)) # revealed: T@decorated + +@transparent_decorator +def decorated[T](t: T) -> None: + # error: [redundant-cast] + reveal_type(cast(T, t)) # revealed: T@decorated +``` diff --git a/crates/ty_python_semantic/resources/mdtest/named_tuple.md b/crates/ty_python_semantic/resources/mdtest/named_tuple.md index 175e42fc51..2145361323 100644 --- a/crates/ty_python_semantic/resources/mdtest/named_tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/named_tuple.md @@ -150,9 +150,9 @@ class Person(NamedTuple): reveal_type(Person._field_defaults) # revealed: dict[str, Any] reveal_type(Person._fields) # revealed: tuple[str, ...] -reveal_type(Person._make) # revealed: bound method ._make(iterable: Iterable[Any]) -> Self@NamedTupleFallback +reveal_type(Person._make) # revealed: bound method ._make(iterable: Iterable[Any]) -> Self@_make reveal_type(Person._asdict) # revealed: def _asdict(self) -> dict[str, Any] -reveal_type(Person._replace) # revealed: def _replace(self, **kwargs: Any) -> Self@NamedTupleFallback +reveal_type(Person._replace) # revealed: def _replace(self, **kwargs: Any) -> Self@_replace # TODO: should be `Person` once we support `Self` reveal_type(Person._make(("Alice", 42))) # revealed: Unknown diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 3bccbf2a1b..04c3838074 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -47,8 +47,8 @@ use crate::types::function::{ DataclassTransformerParams, FunctionSpans, FunctionType, KnownFunction, }; use crate::types::generics::{ - GenericContext, PartialSpecialization, Specialization, walk_generic_context, - walk_partial_specialization, walk_specialization, + GenericContext, PartialSpecialization, Specialization, bind_legacy_typevar, + walk_generic_context, walk_partial_specialization, walk_specialization, }; pub use crate::types::ide_support::{ CallSignatureDetails, Member, all_members, call_signature_details, definition_kind_for_name, @@ -5268,12 +5268,13 @@ impl<'db> Type<'db> { /// expression, it names the type `Type::NominalInstance(builtins.int)`, that is, all objects whose /// `__class__` is `int`. /// - /// The `scope_id` argument must always be a scope from the file we are currently inferring, so + /// The `scope_id` and `legacy_typevar_binding_context` arguments must always come from the file we are currently inferring, so /// as to avoid cross-module AST dependency. pub(crate) fn in_type_expression( &self, db: &'db dyn Db, scope_id: ScopeId<'db>, + legacy_typevar_binding_context: Option>, ) -> Result, InvalidTypeExpressionError<'db>> { match self { // Special cases for `float` and `complex` @@ -5402,16 +5403,26 @@ impl<'db> Type<'db> { "nearest_enclosing_class must return type that can be instantiated", ); let class_definition = class.definition(db); - Ok(Type::TypeVar(TypeVarInstance::new( + let typevar = TypeVarInstance::new( db, ast::name::Name::new_static("Self"), Some(class_definition), - Some(class_definition), + None, Some(TypeVarBoundOrConstraints::UpperBound(instance)), TypeVarVariance::Invariant, None, TypeVarKind::Implicit, - ))) + ); + let typevar = bind_legacy_typevar( + db, + &module, + index, + scope_id.file_scope_id(db), + legacy_typevar_binding_context, + typevar, + ) + .unwrap_or(typevar); + Ok(Type::TypeVar(typevar)) } SpecialFormType::TypeAlias => Ok(Type::Dynamic(DynamicType::TodoTypeAlias)), SpecialFormType::TypedDict => Err(InvalidTypeExpressionError { @@ -5486,7 +5497,7 @@ impl<'db> Type<'db> { let mut builder = UnionBuilder::new(db); let mut invalid_expressions = smallvec::SmallVec::default(); for element in union.elements(db) { - match element.in_type_expression(db, scope_id) { + match element.in_type_expression(db, scope_id, legacy_typevar_binding_context) { Ok(type_expr) => builder = builder.add(type_expr), Err(InvalidTypeExpressionError { fallback_type, @@ -6660,7 +6671,7 @@ impl<'db> InvalidTypeExpression<'db> { return; }; if module_member_with_same_name - .in_type_expression(db, scope) + .in_type_expression(db, scope, None) .is_err() { return; diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 501a59d432..a9d3ff5231 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -9,6 +9,7 @@ use crate::semantic_index::scope::{FileScopeId, NodeWithScopeKind}; use crate::semantic_index::{SemanticIndex, semantic_index}; use crate::types::class::ClassType; use crate::types::class_base::ClassBase; +use crate::types::infer::infer_definition_types; use crate::types::instance::{NominalInstanceType, Protocol, ProtocolInstanceType}; use crate::types::signatures::{Parameter, Parameters, Signature}; use crate::types::tuple::{TupleSpec, TupleType}; @@ -20,7 +21,7 @@ use crate::{Db, FxOrderSet}; /// Returns an iterator of any generic context introduced by the given scope or any enclosing /// scope. -pub(crate) fn enclosing_generic_contexts<'db>( +fn enclosing_generic_contexts<'db>( db: &'db dyn Db, module: &ParsedModuleRef, index: &SemanticIndex<'db>, @@ -35,7 +36,9 @@ pub(crate) fn enclosing_generic_contexts<'db>( .generic_context(db) } NodeWithScopeKind::Function(function) => { - binding_type(db, index.expect_single_definition(function.node(module))) + infer_definition_types(db, index.expect_single_definition(function.node(module))) + .undecorated_type() + .expect("function should have undecorated type") .into_function_literal()? .signature(db) .iter() @@ -59,6 +62,37 @@ fn bound_legacy_typevars<'db>( .filter(|typevar| typevar.is_legacy(db)) } +/// Binds an unbound legacy typevar. +/// +/// When a legacy typevar is first created, we will have a [`TypeVarInstance`] which does not have +/// an associated binding context. When the typevar is used in a generic class or function, we +/// "bind" it, adding the [`Definition`] of the generic class or function as its "binding context". +/// +/// When an expression resolves to a legacy typevar, our inferred type will refer to the unbound +/// [`TypeVarInstance`] from when the typevar was first created. This function walks the scopes +/// that enclosing the expression, looking for the innermost binding context that binds the +/// typevar. +/// +/// If no enclosing scope has already bound the typevar, we might be in a syntactic position that +/// is about to bind it (indicated by a non-`None` `legacy_typevar_binding_context`), in which case +/// we bind the typevar with that new binding context. +pub(crate) fn bind_legacy_typevar<'db>( + db: &'db dyn Db, + module: &ParsedModuleRef, + index: &SemanticIndex<'db>, + containing_scope: FileScopeId, + legacy_typevar_binding_context: Option>, + typevar: TypeVarInstance<'db>, +) -> Option> { + enclosing_generic_contexts(db, module, index, containing_scope) + .find_map(|enclosing_context| enclosing_context.binds_legacy_typevar(db, typevar)) + .or_else(|| { + legacy_typevar_binding_context.map(|legacy_typevar_binding_context| { + typevar.with_binding_context(db, legacy_typevar_binding_context) + }) + }) +} + /// A list of formal type variables for a generic function, class, or type alias. /// /// TODO: Handle nested generic contexts better, with actual parent links to the lexically @@ -259,12 +293,13 @@ impl<'db> GenericContext<'db> { db: &'db dyn Db, typevar: TypeVarInstance<'db>, ) -> Option> { - assert!(typevar.is_legacy(db)); + assert!(typevar.is_legacy(db) || typevar.is_implicit(db)); let typevar_def = typevar.definition(db); self.variables(db) .iter() .find(|self_typevar| { - self_typevar.is_legacy(db) && self_typevar.definition(db) == typevar_def + (self_typevar.is_legacy(db) || self_typevar.is_implicit(db)) + && self_typevar.definition(db) == typevar_def }) .copied() } diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index b618a6a692..29dd5cc6b1 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -110,7 +110,7 @@ use crate::types::enums::is_enum_class; use crate::types::function::{ FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral, }; -use crate::types::generics::{GenericContext, enclosing_generic_contexts}; +use crate::types::generics::{GenericContext, bind_legacy_typevar}; use crate::types::mro::MroErrorKind; use crate::types::signatures::{CallableSignature, Signature}; use crate::types::tuple::{TupleSpec, TupleType}; @@ -519,6 +519,9 @@ struct DefinitionInferenceExtra<'db> { /// The diagnostics for this region. diagnostics: TypeCheckDiagnostics, + + /// For function definitions, the undecorated type of the function. + undecorated_type: Option>, } impl<'db> DefinitionInference<'db> { @@ -610,6 +613,10 @@ impl<'db> DefinitionInference<'db> { fn fallback_type(&self) -> Option> { self.is_cycle_callback().then_some(Type::Never) } + + pub(crate) fn undecorated_type(&self) -> Option> { + self.extra.as_ref().and_then(|extra| extra.undecorated_type) + } } /// The inferred types for an expression region. @@ -823,6 +830,9 @@ pub(super) struct TypeInferenceBuilder<'db, 'ast> { /// is a stub file but we're still in a non-deferred region. deferred_state: DeferredExpressionState, + /// For function definitions, the undecorated type of the function. + undecorated_type: Option>, + /// The fallback type for missing expressions/bindings/declarations. /// /// This is used only when constructing a cycle-recovery `TypeInference`. @@ -858,6 +868,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { declarations: VecMap::default(), legacy_typevar_binding_context: None, deferred: VecSet::default(), + undecorated_type: None, cycle_fallback: false, } } @@ -2662,6 +2673,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { function_literal, type_mappings, )); + self.undecorated_type = Some(inferred_ty); for (decorator_ty, decorator_node) in decorator_types_and_nodes.iter().rev() { inferred_ty = match decorator_ty @@ -6427,7 +6439,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { self.infer_place_load(PlaceExprRef::from(&expr), ast::ExprRef::Name(name_node)); resolved - .map_type(|ty| { + .map_type(|ty| match ty { // If the expression resolves to a legacy typevar, we will have the TypeVarInstance // that was created when the typevar was created, which will not have an associated // binding context. If this expression appears inside of a generic context that @@ -6438,40 +6450,31 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // If the legacy typevar is still unbound after that search, and we are in a // context that binds unbound legacy typevars (i.e., the signature of a generic // function), bind it with that context. - let find_legacy_typevar_binding = |typevar: TypeVarInstance<'db>| { - enclosing_generic_contexts( + Type::TypeVar(typevar) if typevar.is_legacy(self.db()) => bind_legacy_typevar( + self.db(), + self.context.module(), + self.index, + self.scope().file_scope_id(self.db()), + self.legacy_typevar_binding_context, + typevar, + ) + .map(Type::TypeVar) + .unwrap_or(ty), + Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) + if typevar.is_legacy(self.db()) => + { + bind_legacy_typevar( self.db(), self.context.module(), self.index, self.scope().file_scope_id(self.db()), + self.legacy_typevar_binding_context, + typevar, ) - .find_map(|enclosing_context| { - enclosing_context.binds_legacy_typevar(self.db(), typevar) - }) - .or_else(|| { - self.legacy_typevar_binding_context - .map(|legacy_typevar_binding_context| { - typevar - .with_binding_context(self.db(), legacy_typevar_binding_context) - }) - }) - }; - - match ty { - Type::TypeVar(typevar) if typevar.is_legacy(self.db()) => { - find_legacy_typevar_binding(typevar) - .map(Type::TypeVar) - .unwrap_or(ty) - } - Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) - if typevar.is_legacy(self.db()) => - { - find_legacy_typevar_binding(typevar) - .map(|typevar| Type::KnownInstance(KnownInstanceType::TypeVar(typevar))) - .unwrap_or(ty) - } - _ => ty, + .map(|typevar| Type::KnownInstance(KnownInstanceType::TypeVar(typevar))) + .unwrap_or(ty) } + _ => ty, }) // Not found in the module's explicitly declared global symbols? // Check the "implicit globals" such as `__doc__`, `__file__`, `__name__`, etc. @@ -9130,6 +9133,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { deferred, cycle_fallback, + // Ignored; only relevant to definition regions + undecorated_type: _, + // builder only state legacy_typevar_binding_context: _, deferred_state: _, @@ -9189,6 +9195,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { declarations, deferred, cycle_fallback, + undecorated_type, // builder only state legacy_typevar_binding_context: _, @@ -9202,14 +9209,18 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let _ = scope; let diagnostics = context.finish(); - let extra = - (!diagnostics.is_empty() || cycle_fallback || !deferred.is_empty()).then(|| { - Box::new(DefinitionInferenceExtra { - cycle_fallback, - deferred: deferred.into_boxed_slice(), - diagnostics, - }) - }); + let extra = (!diagnostics.is_empty() + || cycle_fallback + || undecorated_type.is_some() + || !deferred.is_empty()) + .then(|| { + Box::new(DefinitionInferenceExtra { + cycle_fallback, + deferred: deferred.into_boxed_slice(), + diagnostics, + undecorated_type, + }) + }); if bindings.len() > 20 { tracing::debug!( @@ -9253,6 +9264,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { bindings: _, declarations: _, + // Ignored; only relevant to definition regions + undecorated_type: _, + // Builder only state legacy_typevar_binding_context: _, deferred_state: _, @@ -9360,7 +9374,11 @@ impl<'db> TypeInferenceBuilder<'db, '_> { TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::INIT_VAR) } _ => name_expr_ty - .in_type_expression(self.db(), self.scope()) + .in_type_expression( + self.db(), + self.scope(), + self.legacy_typevar_binding_context, + ) .unwrap_or_else(|error| { error.into_fallback_type( &self.context, @@ -9579,7 +9597,11 @@ impl<'db> TypeInferenceBuilder<'db, '_> { ast::Expr::Name(name) => match name.ctx { ast::ExprContext::Load => self .infer_name_expression(name) - .in_type_expression(self.db(), self.scope()) + .in_type_expression( + self.db(), + self.scope(), + self.legacy_typevar_binding_context, + ) .unwrap_or_else(|error| { error.into_fallback_type( &self.context, @@ -9596,7 +9618,11 @@ impl<'db> TypeInferenceBuilder<'db, '_> { ast::Expr::Attribute(attribute_expression) => match attribute_expression.ctx { ast::ExprContext::Load => self .infer_attribute_expression(attribute_expression) - .in_type_expression(self.db(), self.scope()) + .in_type_expression( + self.db(), + self.scope(), + self.legacy_typevar_binding_context, + ) .unwrap_or_else(|error| { error.into_fallback_type( &self.context, @@ -10282,7 +10308,11 @@ impl<'db> TypeInferenceBuilder<'db, '_> { generic_context, ); specialized_class - .in_type_expression(self.db(), self.scope()) + .in_type_expression( + self.db(), + self.scope(), + self.legacy_typevar_binding_context, + ) .unwrap_or(Type::unknown()) } None => {