diff --git a/crates/red_knot_python_semantic/resources/mdtest/generics.md b/crates/red_knot_python_semantic/resources/mdtest/generics.md index ff9f5ff8d1..d4ccce2ae7 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/generics.md +++ b/crates/red_knot_python_semantic/resources/mdtest/generics.md @@ -6,13 +6,9 @@ Basic PEP 695 generics ```py class MyBox[T]: - # TODO: `T` is defined here - # error: [unresolved-reference] "Name `T` used when not defined" data: T box_model_number = 695 - # TODO: `T` is defined here - # error: [unresolved-reference] "Name `T` used when not defined" def __init__(self, data: T): self.data = data @@ -31,17 +27,12 @@ reveal_type(MyBox.box_model_number) # revealed: Literal[695] ```py class MyBox[T]: - # TODO: `T` is defined here - # error: [unresolved-reference] "Name `T` used when not defined" data: T - # TODO: `T` is defined here - # error: [unresolved-reference] "Name `T` used when not defined" def __init__(self, data: T): self.data = data -# TODO not error on the subscripting or the use of type param -# error: [unresolved-reference] "Name `T` used when not defined" +# TODO not error on the subscripting # error: [non-subscriptable] class MySecureBox[T](MyBox[T]): ... @@ -66,3 +57,55 @@ class S[T](Seq[S]): ... # error: [non-subscriptable] reveal_type(S) # revealed: Literal[S] ``` + +## Type params + +A PEP695 type variable defines a value of type `typing.TypeVar` with attributes `__name__`, +`__bounds__`, `__constraints__`, and `__default__` (the latter three all lazily evaluated): + +```py +def f[T, U: A, V: (A, B), W = A, X: A = A1](): + reveal_type(T) # revealed: TypeVar + reveal_type(T.__name__) # revealed: Literal["T"] + reveal_type(T.__bound__) # revealed: None + reveal_type(T.__constraints__) # revealed: tuple[()] + reveal_type(T.__default__) # revealed: NoDefault + + reveal_type(U) # revealed: TypeVar + reveal_type(U.__name__) # revealed: Literal["U"] + reveal_type(U.__bound__) # revealed: type[A] + reveal_type(U.__constraints__) # revealed: tuple[()] + reveal_type(U.__default__) # revealed: NoDefault + + reveal_type(V) # revealed: TypeVar + reveal_type(V.__name__) # revealed: Literal["V"] + reveal_type(V.__bound__) # revealed: None + reveal_type(V.__constraints__) # revealed: tuple[type[A], type[B]] + reveal_type(V.__default__) # revealed: NoDefault + + reveal_type(W) # revealed: TypeVar + reveal_type(W.__name__) # revealed: Literal["W"] + reveal_type(W.__bound__) # revealed: None + reveal_type(W.__constraints__) # revealed: tuple[()] + reveal_type(W.__default__) # revealed: type[A] + + reveal_type(X) # revealed: TypeVar + reveal_type(X.__name__) # revealed: Literal["X"] + reveal_type(X.__bound__) # revealed: type[A] + reveal_type(X.__constraints__) # revealed: tuple[()] + reveal_type(X.__default__) # revealed: type[A1] + +class A: ... +class B: ... +class A1(A): ... +``` + +## Minimum two constraints + +A typevar with less than two constraints emits a diagnostic and is treated as unconstrained: + +```py +# error: [invalid-typevar-constraints] "TypeVar must have at least two constrained types" +def f[T: (int,)](): + reveal_type(T.__constraints__) # revealed: tuple[()] +``` diff --git a/crates/red_knot_python_semantic/src/semantic_index/builder.rs b/crates/red_knot_python_semantic/src/semantic_index/builder.rs index ba50e16148..fda1f07cbc 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder.rs @@ -373,6 +373,11 @@ impl<'db> SemanticIndexBuilder<'db> { if let Some(default) = default { self.visit_expr(default); } + match type_param { + ast::TypeParam::TypeVar(node) => self.add_definition(symbol, node), + ast::TypeParam::ParamSpec(node) => self.add_definition(symbol, node), + ast::TypeParam::TypeVarTuple(node) => self.add_definition(symbol, node), + }; } } diff --git a/crates/red_knot_python_semantic/src/semantic_index/definition.rs b/crates/red_knot_python_semantic/src/semantic_index/definition.rs index d165e521e8..11822e5d75 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/definition.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/definition.rs @@ -92,6 +92,9 @@ pub(crate) enum DefinitionNodeRef<'a> { WithItem(WithItemDefinitionNodeRef<'a>), MatchPattern(MatchPatternDefinitionNodeRef<'a>), ExceptHandler(ExceptHandlerDefinitionNodeRef<'a>), + TypeVar(&'a ast::TypeParamTypeVar), + ParamSpec(&'a ast::TypeParamParamSpec), + TypeVarTuple(&'a ast::TypeParamTypeVarTuple), } impl<'a> From<&'a ast::StmtFunctionDef> for DefinitionNodeRef<'a> { @@ -130,6 +133,24 @@ impl<'a> From<&'a ast::Alias> for DefinitionNodeRef<'a> { } } +impl<'a> From<&'a ast::TypeParamTypeVar> for DefinitionNodeRef<'a> { + fn from(value: &'a ast::TypeParamTypeVar) -> Self { + Self::TypeVar(value) + } +} + +impl<'a> From<&'a ast::TypeParamParamSpec> for DefinitionNodeRef<'a> { + fn from(value: &'a ast::TypeParamParamSpec) -> Self { + Self::ParamSpec(value) + } +} + +impl<'a> From<&'a ast::TypeParamTypeVarTuple> for DefinitionNodeRef<'a> { + fn from(value: &'a ast::TypeParamTypeVarTuple) -> Self { + Self::TypeVarTuple(value) + } +} + impl<'a> From> for DefinitionNodeRef<'a> { fn from(node_ref: ImportFromDefinitionNodeRef<'a>) -> Self { Self::ImportFrom(node_ref) @@ -317,6 +338,15 @@ impl<'db> DefinitionNodeRef<'db> { handler: AstNodeRef::new(parsed, handler), is_star, }), + DefinitionNodeRef::TypeVar(node) => { + DefinitionKind::TypeVar(AstNodeRef::new(parsed, node)) + } + DefinitionNodeRef::ParamSpec(node) => { + DefinitionKind::ParamSpec(AstNodeRef::new(parsed, node)) + } + DefinitionNodeRef::TypeVarTuple(node) => { + DefinitionKind::TypeVarTuple(AstNodeRef::new(parsed, node)) + } } } @@ -356,6 +386,9 @@ impl<'db> DefinitionNodeRef<'db> { identifier.into() } Self::ExceptHandler(ExceptHandlerDefinitionNodeRef { handler, .. }) => handler.into(), + Self::TypeVar(node) => node.into(), + Self::ParamSpec(node) => node.into(), + Self::TypeVarTuple(node) => node.into(), } } } @@ -412,6 +445,9 @@ pub enum DefinitionKind<'db> { WithItem(WithItemDefinitionKind), MatchPattern(MatchPatternDefinitionKind), ExceptHandler(ExceptHandlerDefinitionKind), + TypeVar(AstNodeRef), + ParamSpec(AstNodeRef), + TypeVarTuple(AstNodeRef), } impl DefinitionKind<'_> { @@ -421,7 +457,10 @@ impl DefinitionKind<'_> { DefinitionKind::Function(_) | DefinitionKind::Class(_) | DefinitionKind::Import(_) - | DefinitionKind::ImportFrom(_) => DefinitionCategory::DeclarationAndBinding, + | DefinitionKind::ImportFrom(_) + | DefinitionKind::TypeVar(_) + | DefinitionKind::ParamSpec(_) + | DefinitionKind::TypeVarTuple(_) => DefinitionCategory::DeclarationAndBinding, // a parameter always binds a value, but is only a declaration if annotated DefinitionKind::Parameter(parameter) => { if parameter.annotation.is_some() { @@ -696,3 +735,21 @@ impl From<&ast::ExceptHandlerExceptHandler> for DefinitionNodeKey { Self(NodeKey::from_node(handler)) } } + +impl From<&ast::TypeParamTypeVar> for DefinitionNodeKey { + fn from(value: &ast::TypeParamTypeVar) -> Self { + Self(NodeKey::from_node(value)) + } +} + +impl From<&ast::TypeParamParamSpec> for DefinitionNodeKey { + fn from(value: &ast::TypeParamParamSpec) -> Self { + Self(NodeKey::from_node(value)) + } +} + +impl From<&ast::TypeParamTypeVarTuple> for DefinitionNodeKey { + fn from(value: &ast::TypeParamTypeVarTuple) -> Self { + Self(NodeKey::from_node(value)) + } +} diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 46a4d97475..947ccb801c 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -307,7 +307,7 @@ fn declarations_ty<'db>( } /// Representation of a type: a set of possible values at runtime. -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, salsa::Update)] pub enum Type<'db> { /// The dynamic type: a statically unknown set of values Any, @@ -336,7 +336,7 @@ pub enum Type<'db> { /// The set of Python objects with the given class in their __class__'s method resolution order Instance(InstanceType<'db>), /// A single Python object that requires special treatment in the type system - KnownInstance(KnownInstanceType), + KnownInstance(KnownInstanceType<'db>), /// The set of objects in any of the types in the union Union(UnionType<'db>), /// The set of objects in all of the types in the intersection @@ -657,17 +657,23 @@ impl<'db> Type<'db> { // TODO: Once we have support for final classes, we can establish that // `Type::SubclassOf('FinalClass')` is equivalent to `Type::ClassLiteral('FinalClass')`. - // TODO: The following is a workaround that is required to unify the two different - // versions of `NoneType` in typeshed. This should not be required anymore once we - // understand `sys.version_info` branches. + // TODO: The following is a workaround that is required to unify the two different versions + // of `NoneType` and `NoDefaultType` in typeshed. This should not be required anymore once + // we understand `sys.version_info` branches. self == other || matches!((self, other), ( Type::Instance(InstanceType { class: self_class }), Type::Instance(InstanceType { class: target_class }) ) - if self_class.is_known(db, KnownClass::NoneType) && - target_class.is_known(db, KnownClass::NoneType)) + if ( + self_class.is_known(db, KnownClass::NoneType) && + target_class.is_known(db, KnownClass::NoneType) + ) || ( + self_class.is_known(db, KnownClass::NoDefaultType) && + target_class.is_known(db, KnownClass::NoDefaultType) + ) + ) } /// Return true if this type and `other` have no common elements. @@ -907,8 +913,11 @@ impl<'db> Type<'db> { | Type::ModuleLiteral(..) | Type::KnownInstance(..) => true, Type::Instance(InstanceType { class }) => { - // TODO some more instance types can be singleton types (EllipsisType, NotImplementedType) - matches!(class.known(db), Some(KnownClass::NoneType)) + if let Some(known_class) = class.known(db) { + known_class.is_singleton() + } else { + false + } } Type::Tuple(..) => { // The empty tuple is a singleton on CPython and PyPy, but not on other Python @@ -961,7 +970,7 @@ impl<'db> Type<'db> { .all(|elem| elem.is_single_valued(db)), Type::Instance(InstanceType { class }) => match class.known(db) { - Some(KnownClass::NoneType) => true, + Some(KnownClass::NoneType | KnownClass::NoDefaultType) => true, Some( KnownClass::Bool | KnownClass::Object @@ -978,7 +987,8 @@ impl<'db> Type<'db> { | KnownClass::GenericAlias | KnownClass::ModuleType | KnownClass::FunctionType - | KnownClass::SpecialForm, + | KnownClass::SpecialForm + | KnownClass::TypeVar, ) => false, None => false, }, @@ -1049,9 +1059,7 @@ impl<'db> Type<'db> { } Type::ClassLiteral(class_ty) => class_ty.member(db, name), Type::SubclassOf(subclass_of_ty) => subclass_of_ty.member(db, name), - Type::KnownInstance(known_instance) => { - known_instance.instance_fallback(db).member(db, name) - } + Type::KnownInstance(known_instance) => known_instance.member(db, name), Type::Instance(_) => { // TODO MRO? get_own_instance_member, get_instance_member Type::Todo.into() @@ -1442,7 +1450,7 @@ impl<'db> Type<'db> { Type::IntLiteral(_) | Type::BooleanLiteral(_) => self.repr(db), Type::StringLiteral(_) | Type::LiteralString => *self, Type::KnownInstance(known_instance) => { - Type::StringLiteral(StringLiteralType::new(db, known_instance.repr())) + Type::StringLiteral(StringLiteralType::new(db, known_instance.repr(db))) } // TODO: handle more complex types _ => KnownClass::Str.to_instance(db), @@ -1464,7 +1472,7 @@ impl<'db> Type<'db> { })), Type::LiteralString => Type::LiteralString, Type::KnownInstance(known_instance) => { - Type::StringLiteral(StringLiteralType::new(db, known_instance.repr())) + Type::StringLiteral(StringLiteralType::new(db, known_instance.repr(db))) } // TODO: handle more complex types _ => KnownClass::Str.to_instance(db), @@ -1514,7 +1522,10 @@ pub enum KnownClass { FunctionType, // Typeshed NoneType, // Part of `types` for Python >= 3.10 + // Typing SpecialForm, + TypeVar, + NoDefaultType, } impl<'db> KnownClass { @@ -1537,6 +1548,8 @@ impl<'db> KnownClass { Self::FunctionType => "FunctionType", Self::NoneType => "NoneType", Self::SpecialForm => "_SpecialForm", + Self::TypeVar => "TypeVar", + Self::NoDefaultType => "_NoDefaultType", } } @@ -1561,8 +1574,34 @@ impl<'db> KnownClass { Self::GenericAlias | Self::ModuleType | Self::FunctionType => { types_symbol(db, self.as_str()).unwrap_or_unknown() } - Self::SpecialForm => typing_symbol(db, self.as_str()).unwrap_or_unknown(), Self::NoneType => typeshed_symbol(db, self.as_str()).unwrap_or_unknown(), + Self::SpecialForm => typing_symbol(db, self.as_str()).unwrap_or_unknown(), + Self::TypeVar => typing_symbol(db, self.as_str()).unwrap_or_unknown(), + Self::NoDefaultType => typing_extensions_symbol(db, self.as_str()).unwrap_or_unknown(), + } + } + + fn is_singleton(self) -> bool { + // TODO there are other singleton types (EllipsisType, NotImplementedType) + match self { + Self::NoneType | Self::NoDefaultType => true, + Self::Bool + | Self::Object + | Self::Bytes + | Self::Tuple + | Self::Int + | Self::Float + | Self::Str + | Self::Set + | Self::Dict + | Self::List + | Self::Type + | Self::Slice + | Self::GenericAlias + | Self::ModuleType + | Self::FunctionType + | Self::SpecialForm + | Self::TypeVar => false, } } @@ -1597,6 +1636,7 @@ impl<'db> KnownClass { "ModuleType" => Some(Self::ModuleType), "FunctionType" => Some(Self::FunctionType), "_SpecialForm" => Some(Self::SpecialForm), + "_NoDefaultType" => Some(Self::NoDefaultType), _ => None, } } @@ -1621,7 +1661,7 @@ impl<'db> KnownClass { | Self::Slice => module.name() == "builtins", Self::GenericAlias | Self::ModuleType | Self::FunctionType => module.name() == "types", Self::NoneType => matches!(module.name().as_str(), "_typeshed" | "types"), - Self::SpecialForm => { + Self::SpecialForm | Self::TypeVar | Self::NoDefaultType => { matches!(module.name().as_str(), "typing" | "typing_extensions") } } @@ -1629,17 +1669,20 @@ impl<'db> KnownClass { } /// Enumeration of specific runtime that are special enough to be considered their own type. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum KnownInstanceType { +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)] +pub enum KnownInstanceType<'db> { /// The symbol `typing.Literal` (which can also be found as `typing_extensions.Literal`) Literal, + /// A single instance of `typing.TypeVar` + TypeVar(TypeVarInstance<'db>), // TODO: fill this enum out with more special forms, etc. } -impl KnownInstanceType { +impl<'db> KnownInstanceType<'db> { pub const fn as_str(self) -> &'static str { match self { KnownInstanceType::Literal => "Literal", + KnownInstanceType::TypeVar(_) => "TypeVar", } } @@ -1647,13 +1690,15 @@ impl KnownInstanceType { pub const fn bool(self) -> Truthiness { match self { Self::Literal => Truthiness::AlwaysTrue, + Self::TypeVar(_) => Truthiness::AlwaysTrue, } } /// Return the repr of the symbol at runtime - pub const fn repr(self) -> &'static str { + pub fn repr(self, db: &'db dyn Db) -> &'db str { match self { Self::Literal => "typing.Literal", + Self::TypeVar(typevar) => typevar.name(db), } } @@ -1661,6 +1706,7 @@ impl KnownInstanceType { pub const fn class(self) -> KnownClass { match self { Self::Literal => KnownClass::SpecialForm, + Self::TypeVar(_) => KnownClass::TypeVar, } } @@ -1682,6 +1728,93 @@ impl KnownInstanceType { _ => None, } } + + fn member(self, db: &'db dyn Db, name: &str) -> Symbol<'db> { + match (self, name) { + (Self::TypeVar(typevar), "__name__") => Symbol::Type( + Type::StringLiteral(StringLiteralType::new(db, typevar.name(db).as_str())), + Boundness::Bound, + ), + (Self::TypeVar(typevar), "__bound__") => Symbol::Type( + typevar + .upper_bound(db) + .map(|ty| ty.to_meta_type(db)) + .unwrap_or(KnownClass::NoneType.to_instance(db)), + Boundness::Bound, + ), + (Self::TypeVar(typevar), "__constraints__") => Symbol::Type( + Type::Tuple(TupleType::new( + db, + typevar + .constraints(db) + .map(|constraints| { + constraints + .iter() + .map(|ty| ty.to_meta_type(db)) + .collect::>() + }) + .unwrap_or_else(|| std::iter::empty().collect::>()), + )), + Boundness::Bound, + ), + (Self::TypeVar(typevar), "__default__") => Symbol::Type( + typevar + .default_ty(db) + .map(|ty| ty.to_meta_type(db)) + .unwrap_or_else(|| KnownClass::NoDefaultType.to_instance(db)), + Boundness::Bound, + ), + _ => self.instance_fallback(db).member(db, name), + } + } +} + +/// Data regarding a single type variable. +/// +/// This is referenced by `KnownInstanceType::TypeVar` (to represent the singleton type of the +/// runtime `typing.TypeVar` object itself). In the future, it will also be referenced also by a +/// new `Type` variant to represent the type that this typevar represents as an annotation: that +/// is, an unknown set of objects, constrained by the upper-bound/constraints on this type var, +/// defaulting to the default type of this type var when not otherwise bound to a type. +/// +/// This must be a tracked struct, not an interned one, because typevar equivalence is by identity, +/// not by value. Two typevars that have the same name, bound/constraints, and default, are still +/// different typevars: if used in the same scope, they may be bound to different types. +#[salsa::tracked] +pub struct TypeVarInstance<'db> { + /// The name of this TypeVar (e.g. `T`) + #[return_ref] + name: ast::name::Name, + + /// The upper bound or constraint on the type of this TypeVar + bound_or_constraints: Option>, + + /// The default type for this TypeVar + default_ty: Option>, +} + +impl<'db> TypeVarInstance<'db> { + pub(crate) fn upper_bound(self, db: &'db dyn Db) -> Option> { + if let Some(TypeVarBoundOrConstraints::UpperBound(ty)) = self.bound_or_constraints(db) { + Some(ty) + } else { + None + } + } + + pub(crate) fn constraints(self, db: &'db dyn Db) -> Option<&[Type<'db>]> { + if let Some(TypeVarBoundOrConstraints::Constraints(tuple)) = self.bound_or_constraints(db) { + Some(tuple.elements(db)) + } else { + None + } + } +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq, salsa::Update)] +pub enum TypeVarBoundOrConstraints<'db> { + UpperBound(Type<'db>), + Constraints(TupleType<'db>), } #[derive(Debug, Clone, PartialEq, Eq)] @@ -2499,7 +2632,7 @@ impl SeenSet { } /// A singleton type representing a single class object at runtime. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)] pub struct ClassLiteralType<'db> { class: Class<'db>, } @@ -2521,7 +2654,7 @@ impl<'db> From> for Type<'db> { } /// A type that represents `type[C]`, i.e. the class literal `C` and class literals that are subclasses of `C`. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)] pub struct SubclassOfType<'db> { class: Class<'db>, } @@ -2533,7 +2666,7 @@ impl<'db> SubclassOfType<'db> { } /// A type representing the set of runtime objects which are instances of a certain class. -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update)] pub struct InstanceType<'db> { class: Class<'db>, } @@ -2746,9 +2879,10 @@ mod tests { // BuiltinInstance("str") corresponds to an instance of the builtin `str` class BuiltinInstance(&'static str), TypingInstance(&'static str), - KnownInstance(KnownInstanceType), + TypingLiteral, // BuiltinClassLiteral("str") corresponds to the builtin `str` class object itself BuiltinClassLiteral(&'static str), + KnownClassInstance(KnownClass), Union(Vec), Intersection { pos: Vec, neg: Vec }, Tuple(Vec), @@ -2769,8 +2903,9 @@ mod tests { Ty::BytesLiteral(s) => Type::BytesLiteral(BytesLiteralType::new(db, s.as_bytes())), Ty::BuiltinInstance(s) => builtins_symbol(db, s).expect_type().to_instance(db), Ty::TypingInstance(s) => typing_symbol(db, s).expect_type().to_instance(db), - Ty::KnownInstance(known_instance) => Type::KnownInstance(known_instance), + Ty::TypingLiteral => Type::KnownInstance(KnownInstanceType::Literal), Ty::BuiltinClassLiteral(s) => builtins_symbol(db, s).expect_type(), + Ty::KnownClassInstance(known_class) => known_class.to_instance(db), Ty::Union(tys) => { UnionType::from_elements(db, tys.into_iter().map(|ty| ty.into_type(db))) } @@ -2876,14 +3011,8 @@ mod tests { #[test_case(Ty::Intersection{pos: vec![Ty::BuiltinInstance("str")], neg: vec![Ty::StringLiteral("foo")]}, Ty::Intersection{pos: vec![], neg: vec![Ty::IntLiteral(2)]})] #[test_case(Ty::BuiltinClassLiteral("int"), Ty::BuiltinClassLiteral("int"))] #[test_case(Ty::BuiltinClassLiteral("int"), Ty::BuiltinInstance("object"))] - #[test_case( - Ty::KnownInstance(KnownInstanceType::Literal), - Ty::TypingInstance("_SpecialForm") - )] - #[test_case( - Ty::KnownInstance(KnownInstanceType::Literal), - Ty::BuiltinInstance("object") - )] + #[test_case(Ty::TypingLiteral, Ty::TypingInstance("_SpecialForm"))] + #[test_case(Ty::TypingLiteral, Ty::BuiltinInstance("object"))] fn is_subtype_of(from: Ty, to: Ty) { let db = setup_db(); assert!(from.into_type(&db).is_subtype_of(&db, to.into_type(&db))); @@ -3126,6 +3255,7 @@ mod tests { #[test_case(Ty::None)] #[test_case(Ty::BooleanLiteral(true))] #[test_case(Ty::BooleanLiteral(false))] + #[test_case(Ty::KnownClassInstance(KnownClass::NoDefaultType))] fn is_singleton(from: Ty) { let db = setup_db(); diff --git a/crates/red_knot_python_semantic/src/types/display.rs b/crates/red_knot_python_semantic/src/types/display.rs index 6818fe12d6..5bcafd902e 100644 --- a/crates/red_knot_python_semantic/src/types/display.rs +++ b/crates/red_knot_python_semantic/src/types/display.rs @@ -71,6 +71,11 @@ impl Display for DisplayRepresentation<'_> { { f.write_str("None") } + Type::Instance(InstanceType { class }) + if class.is_known(self.db, KnownClass::NoDefaultType) => + { + f.write_str("NoDefault") + } // `[Type::Todo]`'s display should be explicit that is not a valid display of // any other type Type::Todo => f.write_str("@Todo"), diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index f68f502e25..30e9965e73 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -59,7 +59,8 @@ use crate::types::{ Boundness, BytesLiteralType, Class, ClassLiteralType, FunctionType, InstanceType, IntersectionBuilder, IntersectionType, IterationOutcome, KnownClass, KnownFunction, KnownInstanceType, MetaclassCandidate, MetaclassErrorKind, SliceLiteralType, StringLiteralType, - Symbol, Truthiness, TupleType, Type, TypeArrayDisplay, UnionBuilder, UnionType, + Symbol, Truthiness, TupleType, Type, TypeArrayDisplay, TypeVarBoundOrConstraints, + TypeVarInstance, UnionBuilder, UnionType, }; use crate::unpack::Unpack; use crate::util::subscript::{PyIndex, PySlice}; @@ -642,6 +643,15 @@ impl<'db> TypeInferenceBuilder<'db> { DefinitionKind::ExceptHandler(except_handler_definition) => { self.infer_except_handler_definition(except_handler_definition, definition); } + DefinitionKind::TypeVar(node) => { + self.infer_typevar_definition(node, definition); + } + DefinitionKind::ParamSpec(node) => { + self.infer_paramspec_definition(node, definition); + } + DefinitionKind::TypeVarTuple(node) => { + self.infer_typevartuple_definition(node, definition); + } } } @@ -1352,6 +1362,82 @@ impl<'db> TypeInferenceBuilder<'db> { ); } + fn infer_typevar_definition( + &mut self, + node: &ast::TypeParamTypeVar, + definition: Definition<'db>, + ) { + let ast::TypeParamTypeVar { + range: _, + name, + bound, + default, + } = node; + let bound_or_constraint = match bound.as_deref() { + Some(expr @ ast::Expr::Tuple(ast::ExprTuple { elts, .. })) => { + if elts.len() < 2 { + self.diagnostics.add( + expr.into(), + "invalid-typevar-constraints", + format_args!("TypeVar must have at least two constrained types"), + ); + self.infer_expression(expr); + None + } else { + let tuple = TupleType::new( + self.db, + elts.iter() + .map(|expr| self.infer_type_expression(expr)) + .collect::>(), + ); + let constraints = TypeVarBoundOrConstraints::Constraints(tuple); + self.store_expression_type(expr, Type::Tuple(tuple)); + Some(constraints) + } + } + Some(expr) => Some(TypeVarBoundOrConstraints::UpperBound( + self.infer_type_expression(expr), + )), + None => None, + }; + let default_ty = self.infer_optional_type_expression(default.as_deref()); + let ty = Type::KnownInstance(KnownInstanceType::TypeVar(TypeVarInstance::new( + self.db, + name.id.clone(), + bound_or_constraint, + default_ty, + ))); + self.add_declaration_with_binding(node.into(), definition, ty, ty); + } + + fn infer_paramspec_definition( + &mut self, + node: &ast::TypeParamParamSpec, + definition: Definition<'db>, + ) { + let ast::TypeParamParamSpec { + range: _, + name: _, + default, + } = node; + self.infer_optional_expression(default.as_deref()); + self.add_declaration_with_binding(node.into(), definition, Type::Todo, Type::Todo); + } + + fn infer_typevartuple_definition( + &mut self, + node: &ast::TypeParamTypeVarTuple, + definition: Definition<'db>, + ) { + let ast::TypeParamTypeVarTuple { + range: _, + name: _, + default, + } = node; + self.infer_optional_expression(default.as_deref()); + self.add_declaration_with_binding(node.into(), definition, Type::Todo, Type::Todo); + } + fn infer_match_statement(&mut self, match_statement: &ast::StmtMatch) { let ast::StmtMatch { range: _, @@ -2013,13 +2099,6 @@ impl<'db> TypeInferenceBuilder<'db> { expression.map(|expr| self.infer_expression(expr)) } - fn infer_optional_annotation_expression( - &mut self, - expr: Option<&ast::Expr>, - ) -> Option> { - expr.map(|expr| self.infer_annotation_expression(expr)) - } - #[track_caller] fn infer_expression(&mut self, expression: &ast::Expr) -> Type<'db> { debug_assert_eq!( @@ -3912,32 +3991,9 @@ impl<'db> TypeInferenceBuilder<'db> { } = type_parameters; for type_param in type_params { match type_param { - ast::TypeParam::TypeVar(typevar) => { - let ast::TypeParamTypeVar { - range: _, - name: _, - bound, - default, - } = typevar; - self.infer_optional_expression(bound.as_deref()); - self.infer_optional_expression(default.as_deref()); - } - ast::TypeParam::ParamSpec(param_spec) => { - let ast::TypeParamParamSpec { - range: _, - name: _, - default, - } = param_spec; - self.infer_optional_expression(default.as_deref()); - } - ast::TypeParam::TypeVarTuple(typevar_tuple) => { - let ast::TypeParamTypeVarTuple { - range: _, - name: _, - default, - } = typevar_tuple; - self.infer_optional_expression(default.as_deref()); - } + ast::TypeParam::TypeVar(node) => self.infer_definition(node), + ast::TypeParam::ParamSpec(node) => self.infer_definition(node), + ast::TypeParam::TypeVarTuple(node) => self.infer_definition(node), } } } @@ -3971,6 +4027,13 @@ impl<'db> TypeInferenceBuilder<'db> { self.store_expression_type(expression, annotation_ty); annotation_ty } + + fn infer_optional_annotation_expression( + &mut self, + expr: Option<&ast::Expr>, + ) -> Option> { + expr.map(|expr| self.infer_annotation_expression(expr)) + } } /// Type expressions @@ -4145,6 +4208,13 @@ impl<'db> TypeInferenceBuilder<'db> { ty } + fn infer_optional_type_expression( + &mut self, + opt_expression: Option<&ast::Expr>, + ) -> Option> { + opt_expression.map(|expr| self.infer_type_expression(expr)) + } + /// Given the slice of a `tuple[]` annotation, return the type that the annotation represents fn infer_tuple_type_expression(&mut self, tuple_slice: &ast::Expr) -> Type<'db> { /// In most cases, if a subelement of the tuple is inferred as `Todo`, @@ -4262,6 +4332,7 @@ impl<'db> TypeInferenceBuilder<'db> { Type::Unknown } }, + KnownInstanceType::TypeVar(_) => Type::Todo, } } diff --git a/crates/red_knot_python_semantic/src/types/mro.rs b/crates/red_knot_python_semantic/src/types/mro.rs index c30ad49a62..254a6f03fd 100644 --- a/crates/red_knot_python_semantic/src/types/mro.rs +++ b/crates/red_knot_python_semantic/src/types/mro.rs @@ -381,6 +381,7 @@ impl<'db> ClassBase<'db> { | Type::SubclassOf(_) => None, Type::KnownInstance(known_instance) => match known_instance { KnownInstanceType::Literal => None, + KnownInstanceType::TypeVar(_) => None, }, } } diff --git a/crates/red_knot_workspace/resources/test/corpus/27_func_generic_constraint.py b/crates/red_knot_workspace/resources/test/corpus/27_func_generic_constraint.py index 8f5867f38c..763eea4827 100644 --- a/crates/red_knot_workspace/resources/test/corpus/27_func_generic_constraint.py +++ b/crates/red_knot_workspace/resources/test/corpus/27_func_generic_constraint.py @@ -1,2 +1,5 @@ def foo[T: (str, bytes)](x: T) -> T: ... + +def bar[T: (str,)](x: T) -> T: + ...