From 2d1603970946b2dd755f6e17f19f7e6439c7237b Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Wed, 12 Nov 2025 17:58:07 -0500 Subject: [PATCH] add GenericContext mdtest class --- .../resources/mdtest/annotations/self.md | 12 +++-- .../mdtest/generics/legacy/classes.md | 47 ++++++++++------- .../mdtest/generics/pep695/aliases.md | 16 +++--- .../mdtest/generics/pep695/classes.md | 50 ++++++++++++------- .../resources/mdtest/generics/scoping.md | 6 ++- crates/ty_python_semantic/src/types.rs | 37 ++++++++++---- .../ty_python_semantic/src/types/call/bind.rs | 12 +++-- crates/ty_python_semantic/src/types/class.rs | 16 +++++- .../src/types/class_base.rs | 1 + .../ty_python_semantic/src/types/display.rs | 47 ++++++++++++++--- .../ty_python_semantic/src/types/generics.rs | 5 -- .../types/infer/builder/type_expression.rs | 9 ++++ .../ty_extensions/ty_extensions.pyi | 8 ++- 13 files changed, 190 insertions(+), 76 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/self.md b/crates/ty_python_semantic/resources/mdtest/annotations/self.md index 4d794fe6c4..8fc0802bac 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/self.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/self.md @@ -503,9 +503,11 @@ class C[T](): def f(self: Self): def b(x: Self): reveal_type(x) # revealed: Self@f - reveal_type(generic_context(b)) # revealed: None + # revealed: None + reveal_type(generic_context(b)) -reveal_type(generic_context(C.f)) # revealed: tuple[Self@f] +# revealed: ty_extensions.GenericContext[Self@f] +reveal_type(generic_context(C.f)) ``` Even if the `Self` annotation appears first in the nested function, it is the method that binds @@ -519,9 +521,11 @@ class C: def f(self: "C"): def b(x: Self): reveal_type(x) # revealed: Self@f - reveal_type(generic_context(b)) # revealed: None + # revealed: None + reveal_type(generic_context(b)) -reveal_type(generic_context(C.f)) # revealed: None +# revealed: None +reveal_type(generic_context(C.f)) ``` ## Non-positional first parameters diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md index 7ba6803dda..fcfa28953e 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md @@ -21,14 +21,14 @@ class TypeVarAndParamSpec(Generic[P, T]): ... class SingleTypeVarTuple(Generic[Unpack[Ts]]): ... class TypeVarAndTypeVarTuple(Generic[T, Unpack[Ts]]): ... -# revealed: tuple[T@SingleTypevar] +# revealed: ty_extensions.GenericContext[T@SingleTypevar] reveal_type(generic_context(SingleTypevar)) -# revealed: tuple[T@MultipleTypevars, S@MultipleTypevars] +# revealed: ty_extensions.GenericContext[T@MultipleTypevars, S@MultipleTypevars] reveal_type(generic_context(MultipleTypevars)) -# revealed: tuple[P@SingleParamSpec] +# revealed: ty_extensions.GenericContext[P@SingleParamSpec] reveal_type(generic_context(SingleParamSpec)) -# revealed: tuple[P@TypeVarAndParamSpec, T@TypeVarAndParamSpec] +# revealed: ty_extensions.GenericContext[P@TypeVarAndParamSpec, T@TypeVarAndParamSpec] reveal_type(generic_context(TypeVarAndParamSpec)) # TODO: support `TypeVarTuple` properly (these should not reveal `None`) @@ -66,9 +66,9 @@ class InheritedGeneric(MultipleTypevars[T, S]): ... class InheritedGenericPartiallySpecialized(MultipleTypevars[T, int]): ... class InheritedGenericFullySpecialized(MultipleTypevars[str, int]): ... -# revealed: tuple[T@InheritedGeneric, S@InheritedGeneric] +# revealed: ty_extensions.GenericContext[T@InheritedGeneric, S@InheritedGeneric] reveal_type(generic_context(InheritedGeneric)) -# revealed: tuple[T@InheritedGenericPartiallySpecialized] +# revealed: ty_extensions.GenericContext[T@InheritedGenericPartiallySpecialized] reveal_type(generic_context(InheritedGenericPartiallySpecialized)) # revealed: None reveal_type(generic_context(InheritedGenericFullySpecialized)) @@ -90,7 +90,7 @@ class OuterClass(Generic[T]): # revealed: None reveal_type(generic_context(InnerClassInMethod)) -# revealed: tuple[T@OuterClass] +# revealed: ty_extensions.GenericContext[T@OuterClass] reveal_type(generic_context(OuterClass)) ``` @@ -118,11 +118,11 @@ class ExplicitInheritedGenericPartiallySpecializedExtraTypevar(MultipleTypevars[ # error: [invalid-generic-class] "`Generic` base class must include all type variables used in other base classes" class ExplicitInheritedGenericPartiallySpecializedMissingTypevar(MultipleTypevars[T, int], Generic[S]): ... -# revealed: tuple[T@ExplicitInheritedGeneric, S@ExplicitInheritedGeneric] +# revealed: ty_extensions.GenericContext[T@ExplicitInheritedGeneric, S@ExplicitInheritedGeneric] reveal_type(generic_context(ExplicitInheritedGeneric)) -# revealed: tuple[T@ExplicitInheritedGenericPartiallySpecialized] +# revealed: ty_extensions.GenericContext[T@ExplicitInheritedGenericPartiallySpecialized] reveal_type(generic_context(ExplicitInheritedGenericPartiallySpecialized)) -# revealed: tuple[T@ExplicitInheritedGenericPartiallySpecializedExtraTypevar, S@ExplicitInheritedGenericPartiallySpecializedExtraTypevar] +# revealed: ty_extensions.GenericContext[T@ExplicitInheritedGenericPartiallySpecializedExtraTypevar, S@ExplicitInheritedGenericPartiallySpecializedExtraTypevar] reveal_type(generic_context(ExplicitInheritedGenericPartiallySpecializedExtraTypevar)) ``` @@ -594,18 +594,27 @@ class C(Generic[T]): def generic_method(self, t: T, u: U) -> U: return u -reveal_type(generic_context(C)) # revealed: tuple[T@C] -reveal_type(generic_context(C.method)) # revealed: tuple[Self@method] -reveal_type(generic_context(C.generic_method)) # revealed: tuple[Self@generic_method, U@generic_method] -reveal_type(generic_context(C[int])) # revealed: None -reveal_type(generic_context(C[int].method)) # revealed: tuple[Self@method] -reveal_type(generic_context(C[int].generic_method)) # revealed: tuple[Self@generic_method, U@generic_method] +# revealed: ty_extensions.GenericContext[T@C] +reveal_type(generic_context(C)) +# revealed: ty_extensions.GenericContext[Self@method] +reveal_type(generic_context(C.method)) +# revealed: ty_extensions.GenericContext[Self@generic_method, U@generic_method] +reveal_type(generic_context(C.generic_method)) +# revealed: None +reveal_type(generic_context(C[int])) +# revealed: ty_extensions.GenericContext[Self@method] +reveal_type(generic_context(C[int].method)) +# revealed: ty_extensions.GenericContext[Self@generic_method, U@generic_method] +reveal_type(generic_context(C[int].generic_method)) c: C[int] = C[int]() reveal_type(c.generic_method(1, "string")) # revealed: Literal["string"] -reveal_type(generic_context(c)) # revealed: None -reveal_type(generic_context(c.method)) # revealed: tuple[Self@method] -reveal_type(generic_context(c.generic_method)) # revealed: tuple[Self@generic_method, U@generic_method] +# revealed: None +reveal_type(generic_context(c)) +# revealed: ty_extensions.GenericContext[Self@method] +reveal_type(generic_context(c.method)) +# revealed: ty_extensions.GenericContext[Self@generic_method, U@generic_method] +reveal_type(generic_context(c.generic_method)) ``` ## Specializations propagate diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/aliases.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/aliases.md index 3191cf5683..d32029311a 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/aliases.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/aliases.md @@ -20,17 +20,21 @@ type TypeVarAndParamSpec[T, **P] = ... type SingleTypeVarTuple[*Ts] = ... type TypeVarAndTypeVarTuple[T, *Ts] = ... -# revealed: tuple[T@SingleTypevar] +# revealed: ty_extensions.GenericContext[T@SingleTypevar] reveal_type(generic_context(SingleTypevar)) -# revealed: tuple[T@MultipleTypevars, S@MultipleTypevars] +# revealed: ty_extensions.GenericContext[T@MultipleTypevars, S@MultipleTypevars] reveal_type(generic_context(MultipleTypevars)) # TODO: support `ParamSpec`/`TypeVarTuple` properly # (these should include the `ParamSpec`s and `TypeVarTuple`s in their generic contexts) -reveal_type(generic_context(SingleParamSpec)) # revealed: tuple[()] -reveal_type(generic_context(TypeVarAndParamSpec)) # revealed: tuple[T@TypeVarAndParamSpec] -reveal_type(generic_context(SingleTypeVarTuple)) # revealed: tuple[()] -reveal_type(generic_context(TypeVarAndTypeVarTuple)) # revealed: tuple[T@TypeVarAndTypeVarTuple] +# revealed: ty_extensions.GenericContext[] +reveal_type(generic_context(SingleParamSpec)) +# revealed: ty_extensions.GenericContext[T@TypeVarAndParamSpec] +reveal_type(generic_context(TypeVarAndParamSpec)) +# revealed: ty_extensions.GenericContext[] +reveal_type(generic_context(SingleTypeVarTuple)) +# revealed: ty_extensions.GenericContext[T@TypeVarAndTypeVarTuple] +reveal_type(generic_context(TypeVarAndTypeVarTuple)) ``` You cannot use the same typevar more than once. diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md index 5393d622f5..a701c0fcac 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md @@ -20,17 +20,21 @@ class TypeVarAndParamSpec[T, **P]: ... class SingleTypeVarTuple[*Ts]: ... class TypeVarAndTypeVarTuple[T, *Ts]: ... -# revealed: tuple[T@SingleTypevar] +# revealed: ty_extensions.GenericContext[T@SingleTypevar] reveal_type(generic_context(SingleTypevar)) -# revealed: tuple[T@MultipleTypevars, S@MultipleTypevars] +# revealed: ty_extensions.GenericContext[T@MultipleTypevars, S@MultipleTypevars] reveal_type(generic_context(MultipleTypevars)) # TODO: support `ParamSpec`/`TypeVarTuple` properly # (these should include the `ParamSpec`s and `TypeVarTuple`s in their generic contexts) -reveal_type(generic_context(SingleParamSpec)) # revealed: tuple[()] -reveal_type(generic_context(TypeVarAndParamSpec)) # revealed: tuple[T@TypeVarAndParamSpec] -reveal_type(generic_context(SingleTypeVarTuple)) # revealed: tuple[()] -reveal_type(generic_context(TypeVarAndTypeVarTuple)) # revealed: tuple[T@TypeVarAndTypeVarTuple] +# revealed: ty_extensions.GenericContext[] +reveal_type(generic_context(SingleParamSpec)) +# revealed: ty_extensions.GenericContext[T@TypeVarAndParamSpec] +reveal_type(generic_context(TypeVarAndParamSpec)) +# revealed: ty_extensions.GenericContext[] +reveal_type(generic_context(SingleTypeVarTuple)) +# revealed: ty_extensions.GenericContext[T@TypeVarAndTypeVarTuple] +reveal_type(generic_context(TypeVarAndTypeVarTuple)) ``` You cannot use the same typevar more than once. @@ -49,9 +53,9 @@ class InheritedGeneric[U, V](MultipleTypevars[U, V]): ... class InheritedGenericPartiallySpecialized[U](MultipleTypevars[U, int]): ... class InheritedGenericFullySpecialized(MultipleTypevars[str, int]): ... -# revealed: tuple[U@InheritedGeneric, V@InheritedGeneric] +# revealed: ty_extensions.GenericContext[U@InheritedGeneric, V@InheritedGeneric] reveal_type(generic_context(InheritedGeneric)) -# revealed: tuple[U@InheritedGenericPartiallySpecialized] +# revealed: ty_extensions.GenericContext[U@InheritedGenericPartiallySpecialized] reveal_type(generic_context(InheritedGenericPartiallySpecialized)) # revealed: None reveal_type(generic_context(InheritedGenericFullySpecialized)) @@ -64,7 +68,8 @@ the inheriting class generic. ```py class InheritedGenericDefaultSpecialization(MultipleTypevars): ... -reveal_type(generic_context(InheritedGenericDefaultSpecialization)) # revealed: None +# revealed: None +reveal_type(generic_context(InheritedGenericDefaultSpecialization)) ``` You cannot use PEP-695 syntax and the legacy syntax in the same class definition. @@ -512,18 +517,27 @@ class C[T]: # TODO: error def cannot_shadow_class_typevar[T](self, t: T): ... -reveal_type(generic_context(C)) # revealed: tuple[T@C] -reveal_type(generic_context(C.method)) # revealed: tuple[Self@method] -reveal_type(generic_context(C.generic_method)) # revealed: tuple[Self@generic_method, U@generic_method] -reveal_type(generic_context(C[int])) # revealed: None -reveal_type(generic_context(C[int].method)) # revealed: tuple[Self@method] -reveal_type(generic_context(C[int].generic_method)) # revealed: tuple[Self@generic_method, U@generic_method] +# revealed: ty_extensions.GenericContext[T@C] +reveal_type(generic_context(C)) +# revealed: ty_extensions.GenericContext[Self@method] +reveal_type(generic_context(C.method)) +# revealed: ty_extensions.GenericContext[Self@generic_method, U@generic_method] +reveal_type(generic_context(C.generic_method)) +# revealed: None +reveal_type(generic_context(C[int])) +# revealed: ty_extensions.GenericContext[Self@method] +reveal_type(generic_context(C[int].method)) +# revealed: ty_extensions.GenericContext[Self@generic_method, U@generic_method] +reveal_type(generic_context(C[int].generic_method)) c: C[int] = C[int]() reveal_type(c.generic_method(1, "string")) # revealed: Literal["string"] -reveal_type(generic_context(c)) # revealed: None -reveal_type(generic_context(c.method)) # revealed: tuple[Self@method] -reveal_type(generic_context(c.generic_method)) # revealed: tuple[Self@generic_method, U@generic_method] +# revealed: None +reveal_type(generic_context(c)) +# revealed: ty_extensions.GenericContext[Self@method] +reveal_type(generic_context(c.method)) +# revealed: ty_extensions.GenericContext[Self@generic_method, U@generic_method] +reveal_type(generic_context(c.generic_method)) ``` ## Specializations propagate diff --git a/crates/ty_python_semantic/resources/mdtest/generics/scoping.md b/crates/ty_python_semantic/resources/mdtest/generics/scoping.md index 79944b263a..31c04e0b37 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/scoping.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/scoping.md @@ -154,8 +154,10 @@ from ty_extensions import generic_context legacy.m("string", None) # error: [invalid-argument-type] reveal_type(legacy.m) # revealed: bound method Legacy[int].m[S](x: int, y: S@m) -> S@m -reveal_type(generic_context(Legacy)) # revealed: tuple[T@Legacy] -reveal_type(generic_context(legacy.m)) # revealed: tuple[Self@m, S@m] +# revealed: ty_extensions.GenericContext[T@Legacy] +reveal_type(generic_context(Legacy)) +# revealed: ty_extensions.GenericContext[Self@m, S@m] +reveal_type(generic_context(legacy.m)) ``` With PEP 695 syntax, it is clearer that the method uses a separate typevar: diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 89ae6ff685..d09222eec4 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -6712,6 +6712,10 @@ impl<'db> Type<'db> { invalid_expressions: smallvec::smallvec![InvalidTypeExpression::ConstraintSet], fallback_type: Type::unknown(), }), + KnownInstanceType::GenericContext(__call__) => Err(InvalidTypeExpressionError { + invalid_expressions: smallvec::smallvec![InvalidTypeExpression::GenericContext], + fallback_type: Type::unknown(), + }), KnownInstanceType::SubscriptedProtocol(_) => Err(InvalidTypeExpressionError { invalid_expressions: smallvec::smallvec_inline![ InvalidTypeExpression::Protocol @@ -7977,6 +7981,10 @@ pub enum KnownInstanceType<'db> { /// `ty_extensions.ConstraintSet`. ConstraintSet(TrackedConstraintSet<'db>), + /// A generic context, which is exposed in mdtests as an instance of + /// `ty_extensions.GenericContext`. + GenericContext(GenericContext<'db>), + /// A single instance of `types.UnionType`, which stores the left- and /// right-hand sides of a PEP 604 union. UnionType(InternedTypes<'db>), @@ -8011,7 +8019,9 @@ fn walk_known_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>( KnownInstanceType::TypeAliasType(type_alias) => { visitor.visit_type_alias_type(db, type_alias); } - KnownInstanceType::Deprecated(_) | KnownInstanceType::ConstraintSet(_) => { + KnownInstanceType::Deprecated(_) + | KnownInstanceType::ConstraintSet(_) + | KnownInstanceType::GenericContext(_) => { // Nothing to visit } KnownInstanceType::Field(field) => { @@ -8061,15 +8071,7 @@ impl<'db> KnownInstanceType<'db> { Self::TypeAliasType(type_alias) => { Self::TypeAliasType(type_alias.normalized_impl(db, visitor)) } - Self::Deprecated(deprecated) => { - // Nothing to normalize - Self::Deprecated(deprecated) - } Self::Field(field) => Self::Field(field.normalized_impl(db, visitor)), - Self::ConstraintSet(set) => { - // Nothing to normalize - Self::ConstraintSet(set) - } Self::UnionType(list) => Self::UnionType(list.normalized_impl(db, visitor)), Self::Literal(ty) => Self::Literal(ty.normalized_impl(db, visitor)), Self::Annotated(ty) => Self::Annotated(ty.normalized_impl(db, visitor)), @@ -8078,6 +8080,10 @@ impl<'db> KnownInstanceType<'db> { newtype .map_base_class_type(db, |class_type| class_type.normalized_impl(db, visitor)), ), + Self::Deprecated(_) | Self::ConstraintSet(_) | Self::GenericContext(_) => { + // Nothing to normalize + self + } } } @@ -8095,6 +8101,7 @@ impl<'db> KnownInstanceType<'db> { Self::Deprecated(_) => KnownClass::Deprecated, Self::Field(_) => KnownClass::Field, Self::ConstraintSet(_) => KnownClass::ConstraintSet, + Self::GenericContext(_) => KnownClass::GenericContext, Self::UnionType(_) => KnownClass::UnionType, Self::Literal(_) | Self::Annotated(_) | Self::TypeGenericAlias(_) => { KnownClass::GenericAlias @@ -8179,6 +8186,13 @@ impl<'db> KnownInstanceType<'db> { constraints.display(self.db) ) } + KnownInstanceType::GenericContext(generic_context) => { + write!( + f, + "ty_extensions.GenericContext{}", + generic_context.display_full(self.db) + ) + } KnownInstanceType::UnionType(_) => f.write_str("types.UnionType"), KnownInstanceType::Literal(_) => f.write_str(""), KnownInstanceType::Annotated(_) => { @@ -8423,6 +8437,8 @@ enum InvalidTypeExpression<'db> { Field, /// Same for `ty_extensions.ConstraintSet` ConstraintSet, + /// Same for `ty_extensions.GenericContext` + GenericContext, /// Same for `typing.TypedDict` TypedDict, /// Type qualifiers are always invalid in *type expressions*, @@ -8475,6 +8491,9 @@ impl<'db> InvalidTypeExpression<'db> { InvalidTypeExpression::ConstraintSet => { f.write_str("`ty_extensions.ConstraintSet` is not allowed in type expressions") } + InvalidTypeExpression::GenericContext => { + f.write_str("`ty_extensions.GenericContext` is not allowed in type expressions") + } InvalidTypeExpression::TypedDict => { f.write_str( "The special form `typing.TypedDict` is not allowed in type expressions. \ diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index a99ac8b1ef..d71922b7eb 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -782,6 +782,12 @@ impl<'db> Bindings<'db> { Some(KnownFunction::GenericContext) => { if let [Some(ty)] = overload.parameter_types() { + let wrap_generic_context = |generic_context| { + Type::KnownInstance(KnownInstanceType::GenericContext( + generic_context, + )) + }; + let function_generic_context = |function: FunctionType<'db>| { let union = UnionType::from_elements( db, @@ -790,7 +796,7 @@ impl<'db> Bindings<'db> { .overloads .iter() .filter_map(|signature| signature.generic_context) - .map(|generic_context| generic_context.as_tuple(db)), + .map(wrap_generic_context), ); if union.is_never() { Type::none(db) @@ -804,7 +810,7 @@ impl<'db> Bindings<'db> { overload.set_return_type(match ty { Type::ClassLiteral(class) => class .generic_context(db) - .map(|generic_context| generic_context.as_tuple(db)) + .map(wrap_generic_context) .unwrap_or_else(|| Type::none(db)), Type::FunctionLiteral(function) => { @@ -819,7 +825,7 @@ impl<'db> Bindings<'db> { TypeAliasType::PEP695(alias), )) => alias .generic_context(db) - .map(|generic_context| generic_context.as_tuple(db)) + .map(wrap_generic_context) .unwrap_or_else(|| Type::none(db)), _ => Type::none(db), diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index b23de7d424..9e79b5b279 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -3956,6 +3956,7 @@ pub enum KnownClass { Path, // ty_extensions ConstraintSet, + GenericContext, } impl KnownClass { @@ -4059,6 +4060,7 @@ impl KnownClass { | Self::NamedTupleFallback | Self::NamedTupleLike | Self::ConstraintSet + | Self::GenericContext | Self::ProtocolMeta | Self::TypedDictFallback => Some(Truthiness::Ambiguous), @@ -4142,6 +4144,7 @@ impl KnownClass { | KnownClass::NamedTupleFallback | KnownClass::NamedTupleLike | KnownClass::ConstraintSet + | KnownClass::GenericContext | KnownClass::TypedDictFallback | KnownClass::BuiltinFunctionType | KnownClass::ProtocolMeta @@ -4225,6 +4228,7 @@ impl KnownClass { | KnownClass::NamedTupleFallback | KnownClass::NamedTupleLike | KnownClass::ConstraintSet + | KnownClass::GenericContext | KnownClass::TypedDictFallback | KnownClass::BuiltinFunctionType | KnownClass::ProtocolMeta @@ -4308,6 +4312,7 @@ impl KnownClass { | KnownClass::NamedTupleLike | KnownClass::NamedTupleFallback | KnownClass::ConstraintSet + | KnownClass::GenericContext | KnownClass::BuiltinFunctionType | KnownClass::ProtocolMeta | KnownClass::Template @@ -4402,6 +4407,7 @@ impl KnownClass { | Self::InitVar | Self::NamedTupleFallback | Self::ConstraintSet + | Self::GenericContext | Self::TypedDictFallback | Self::BuiltinFunctionType | Self::ProtocolMeta @@ -4491,6 +4497,7 @@ impl KnownClass { | KnownClass::Template | KnownClass::Path | KnownClass::ConstraintSet + | KnownClass::GenericContext | KnownClass::InitVar => false, KnownClass::NamedTupleFallback | KnownClass::TypedDictFallback => true, } @@ -4599,6 +4606,7 @@ impl KnownClass { Self::NamedTupleFallback => "NamedTupleFallback", Self::NamedTupleLike => "NamedTupleLike", Self::ConstraintSet => "ConstraintSet", + Self::GenericContext => "GenericContext", Self::TypedDictFallback => "TypedDictFallback", Self::Template => "Template", Self::Path => "Path", @@ -4910,7 +4918,9 @@ impl KnownClass { | Self::OrderedDict => KnownModule::Collections, Self::Field | Self::KwOnly | Self::InitVar => KnownModule::Dataclasses, Self::NamedTupleFallback | Self::TypedDictFallback => KnownModule::TypeCheckerInternals, - Self::NamedTupleLike | Self::ConstraintSet => KnownModule::TyExtensions, + Self::NamedTupleLike | Self::ConstraintSet | Self::GenericContext => { + KnownModule::TyExtensions + } Self::Template => KnownModule::Templatelib, Self::Path => KnownModule::Pathlib, } @@ -4993,6 +5003,7 @@ impl KnownClass { | Self::NamedTupleFallback | Self::NamedTupleLike | Self::ConstraintSet + | Self::GenericContext | Self::TypedDictFallback | Self::BuiltinFunctionType | Self::ProtocolMeta @@ -5081,6 +5092,7 @@ impl KnownClass { | Self::NamedTupleFallback | Self::NamedTupleLike | Self::ConstraintSet + | Self::GenericContext | Self::TypedDictFallback | Self::BuiltinFunctionType | Self::ProtocolMeta @@ -5184,6 +5196,7 @@ impl KnownClass { "NamedTupleFallback" => &[Self::NamedTupleFallback], "NamedTupleLike" => &[Self::NamedTupleLike], "ConstraintSet" => &[Self::ConstraintSet], + "GenericContext" => &[Self::GenericContext], "TypedDictFallback" => &[Self::TypedDictFallback], "Template" => &[Self::Template], "Path" => &[Self::Path], @@ -5261,6 +5274,7 @@ impl KnownClass { | Self::ExtensionsTypeVar | Self::NamedTupleLike | Self::ConstraintSet + | Self::GenericContext | Self::Awaitable | Self::Generator | Self::Template diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index 8015dfcf00..d655b76a51 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -174,6 +174,7 @@ impl<'db> ClassBase<'db> { | KnownInstanceType::Deprecated(_) | KnownInstanceType::Field(_) | KnownInstanceType::ConstraintSet(_) + | KnownInstanceType::GenericContext(_) | KnownInstanceType::UnionType(_) | KnownInstanceType::Literal(_) // A class inheriting from a newtype would make intuitive sense, but newtype diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index b8a8a05ac4..9e0bf34a3d 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -892,6 +892,16 @@ impl<'db> GenericContext<'db> { pub fn display(&'db self, db: &'db dyn Db) -> DisplayGenericContext<'db> { Self::display_with(self, db, DisplaySettings::default()) } + + pub fn display_full(&'db self, db: &'db dyn Db) -> DisplayGenericContext<'db> { + DisplayGenericContext { + generic_context: self, + db, + settings: DisplaySettings::default(), + full: true, + } + } + pub fn display_with( &'db self, db: &'db dyn Db, @@ -901,6 +911,7 @@ impl<'db> GenericContext<'db> { generic_context: self, db, settings, + full: false, } } } @@ -914,12 +925,9 @@ struct DisplayOptionalGenericContext<'db> { impl Display for DisplayOptionalGenericContext<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { if let Some(generic_context) = self.generic_context { - DisplayGenericContext { - generic_context, - db: self.db, - settings: self.settings.clone(), - } - .fmt(f) + generic_context + .display_with(self.db, self.settings.clone()) + .fmt(f) } else { Ok(()) } @@ -931,10 +939,11 @@ pub struct DisplayGenericContext<'db> { db: &'db dyn Db, #[expect(dead_code)] settings: DisplaySettings<'db>, + full: bool, } -impl Display for DisplayGenericContext<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { +impl DisplayGenericContext<'_> { + fn fmt_normal(&self, f: &mut Formatter<'_>) -> fmt::Result { let variables = self.generic_context.variables(self.db); let non_implicit_variables: Vec<_> = variables @@ -954,6 +963,28 @@ impl Display for DisplayGenericContext<'_> { } f.write_char(']') } + + fn fmt_full(&self, f: &mut Formatter<'_>) -> fmt::Result { + let variables = self.generic_context.variables(self.db); + f.write_char('[')?; + for (idx, bound_typevar) in variables.enumerate() { + if idx > 0 { + f.write_str(", ")?; + } + bound_typevar.identity(self.db).display(self.db).fmt(f)?; + } + f.write_char(']') + } +} + +impl Display for DisplayGenericContext<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if self.full { + self.fmt_full(f) + } else { + self.fmt_normal(f) + } + } } impl<'db> Specialization<'db> { diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 169b69e496..c2b899b265 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -474,11 +474,6 @@ impl<'db> GenericContext<'db> { self.specialize(db, types.into()) } - /// Returns a tuple type of the typevars introduced by this generic context. - pub(crate) fn as_tuple(self, db: &'db dyn Db) -> Type<'db> { - Type::heterogeneous_tuple(db, self.variables(db).map(Type::TypeVar)) - } - pub(crate) fn is_subset_of(self, db: &'db dyn Db, other: GenericContext<'db>) -> bool { let other_variables = other.variables_inner(db); self.variables(db) diff --git a/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs b/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs index c33c058892..8d14772c49 100644 --- a/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs +++ b/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs @@ -783,6 +783,15 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } Type::unknown() } + KnownInstanceType::GenericContext(_) => { + self.infer_type_expression(slice); + if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { + builder.into_diagnostic(format_args!( + "`ty_extensions.GenericContext` is not allowed in type expressions", + )); + } + Type::unknown() + } KnownInstanceType::TypeVar(_) => { self.infer_type_expression(slice); todo_type!("TypeVar annotations") diff --git a/crates/ty_vendored/ty_extensions/ty_extensions.pyi b/crates/ty_vendored/ty_extensions/ty_extensions.pyi index 744bd5af37..3839665921 100644 --- a/crates/ty_vendored/ty_extensions/ty_extensions.pyi +++ b/crates/ty_vendored/ty_extensions/ty_extensions.pyi @@ -91,6 +91,12 @@ class ConstraintSet: def __or__(self, other: ConstraintSet) -> ConstraintSet: ... def __invert__(self) -> ConstraintSet: ... +class GenericContext: + """ + The set of typevars that are bound by a generic class, function, or type + alias. + """ + # Predicates on types # # Ideally, these would be annotated using `TypeForm`, but that has not been @@ -128,7 +134,7 @@ def is_single_valued(ty: Any) -> bool: # Returns the generic context of a type as a tuple of typevars, or `None` if the # type is not generic. -def generic_context(ty: Any) -> Any: ... +def generic_context(ty: Any) -> GenericContext | None: ... # Returns the `__all__` names of a module as a tuple of sorted strings, or `None` if # either the module does not have `__all__` or it has invalid elements.