diff --git a/crates/red_knot_python_semantic/resources/mdtest/binary/integers.md b/crates/red_knot_python_semantic/resources/mdtest/binary/integers.md index dcbc18031754e2..33072bd6236344 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/binary/integers.md +++ b/crates/red_knot_python_semantic/resources/mdtest/binary/integers.md @@ -50,11 +50,11 @@ reveal_type(1 ** (largest_u32 + 1)) # revealed: int reveal_type(2**largest_u32) # revealed: int def variable(x: int): - reveal_type(x**2) # revealed: int + reveal_type(x**2) # revealed: (int & Any) | (int & float & Any) # TODO: should be `Any` (overload 5 on `__pow__`), requires correct overload matching - reveal_type(2**x) # revealed: int + reveal_type(2**x) # revealed: (int & Any) | (int & float & Any) # TODO: should be `Any` (overload 5 on `__pow__`), requires correct overload matching - reveal_type(x**x) # revealed: int + reveal_type(x**x) # revealed: (int & Any) | (int & float & Any) ``` If the second argument is \<0, a `float` is returned at runtime. If the first argument is \<0 but diff --git a/crates/red_knot_python_semantic/resources/mdtest/dataclasses.md b/crates/red_knot_python_semantic/resources/mdtest/dataclasses.md index 0f23feea9f1c76..8ea2eb9a5c9202 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/dataclasses.md +++ b/crates/red_knot_python_semantic/resources/mdtest/dataclasses.md @@ -570,7 +570,7 @@ class ConvertToLength: class C: converter: ConvertToLength = ConvertToLength() -reveal_type(C.__init__) # revealed: (converter: str = Literal[""]) -> None +reveal_type(C.__init__) # revealed: (converter: str = Never) -> None c = C("abc") reveal_type(c.converter) # revealed: int diff --git a/crates/red_knot_python_semantic/resources/mdtest/descriptor_protocol.md b/crates/red_knot_python_semantic/resources/mdtest/descriptor_protocol.md index 8f8d17ae76be68..d0751b0c14019e 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/descriptor_protocol.md +++ b/crates/red_knot_python_semantic/resources/mdtest/descriptor_protocol.md @@ -459,7 +459,7 @@ class Descriptor: class C: d: Descriptor = Descriptor() -reveal_type(C.d) # revealed: Literal["called on class object"] +reveal_type(C.d) # revealed: Never reveal_type(C().d) # revealed: Literal["called on instance"] ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/function/return_type.md b/crates/red_knot_python_semantic/resources/mdtest/function/return_type.md index 1365807e95b08e..8e83d036543e5e 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/function/return_type.md +++ b/crates/red_knot_python_semantic/resources/mdtest/function/return_type.md @@ -203,7 +203,7 @@ from typing import TypeVar T = TypeVar("T") -# TODO: `invalid-return-type` error should be emitted +# error: [invalid-return-type] def m(x: T) -> T: ... ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/generics/legacy.md b/crates/red_knot_python_semantic/resources/mdtest/generics/legacy.md index 6aea25ed820c2f..17021dd3985baa 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/generics/legacy.md +++ b/crates/red_knot_python_semantic/resources/mdtest/generics/legacy.md @@ -19,6 +19,9 @@ in newer Python releases. from typing import TypeVar T = TypeVar("T") +reveal_type(type(T)) # revealed: Literal[TypeVar] +reveal_type(T) # revealed: typing.TypeVar +reveal_type(T.__name__) # revealed: Literal["T"] ``` ### Directly assigned to a variable @@ -29,7 +32,12 @@ T = TypeVar("T") ```py from typing import TypeVar -# TODO: error +T = TypeVar("T") +# TODO: no error +# error: [invalid-legacy-type-variable] +U: TypeVar = TypeVar("U") + +# error: [invalid-legacy-type-variable] "A legacy `typing.TypeVar` must be immediately assigned to a variable" TestList = list[TypeVar("W")] ``` @@ -40,7 +48,7 @@ TestList = list[TypeVar("W")] ```py from typing import TypeVar -# TODO: error +# error: [invalid-legacy-type-variable] "The name of a legacy `typing.TypeVar` (`Q`) must match the name of the variable it is assigned to (`T`)" T = TypeVar("Q") ``` @@ -57,6 +65,52 @@ T = TypeVar("T") T = TypeVar("T") ``` +### Type variables with a default + +Note that the `__default__` property is only available in Python ≥3.13. + +```toml +[environment] +python-version = "3.13" +``` + +```py +from typing import TypeVar + +T = TypeVar("T", default=int) +reveal_type(T.__default__) # revealed: int +reveal_type(T.__bound__) # revealed: None +reveal_type(T.__constraints__) # revealed: tuple[()] + +S = TypeVar("S") +reveal_type(S.__default__) # revealed: NoDefault +``` + +### Type variables with an upper bound + +```py +from typing import TypeVar + +T = TypeVar("T", bound=int) +reveal_type(T.__bound__) # revealed: int +reveal_type(T.__constraints__) # revealed: tuple[()] + +S = TypeVar("S") +reveal_type(S.__bound__) # revealed: None +``` + +### Type variables with constraints + +```py +from typing import TypeVar + +T = TypeVar("T", int, str) +reveal_type(T.__constraints__) # revealed: tuple[int, str] + +S = TypeVar("S") +reveal_type(S.__constraints__) # revealed: tuple[()] +``` + ### Cannot have only one constraint > `TypeVar` supports constraining parametric types to a fixed set of possible types...There should diff --git a/crates/red_knot_python_semantic/resources/mdtest/generics/pep695.md b/crates/red_knot_python_semantic/resources/mdtest/generics/pep695.md index 67e69940f3129a..67a4fdc4f08844 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/generics/pep695.md +++ b/crates/red_knot_python_semantic/resources/mdtest/generics/pep695.md @@ -17,10 +17,51 @@ instances of `typing.TypeVar`, just like legacy type variables. ```py def f[T](): reveal_type(type(T)) # revealed: Literal[TypeVar] - reveal_type(T) # revealed: T + reveal_type(T) # revealed: typing.TypeVar reveal_type(T.__name__) # revealed: Literal["T"] ``` +### Type variables with a default + +Note that the `__default__` property is only available in Python ≥3.13. + +```toml +[environment] +python-version = "3.13" +``` + +```py +def f[T = int](): + reveal_type(T.__default__) # revealed: int + reveal_type(T.__bound__) # revealed: None + reveal_type(T.__constraints__) # revealed: tuple[()] + +def g[S](): + reveal_type(S.__default__) # revealed: NoDefault +``` + +### Type variables with an upper bound + +```py +def f[T: int](): + reveal_type(T.__bound__) # revealed: int + reveal_type(T.__constraints__) # revealed: tuple[()] + +def g[S](): + reveal_type(S.__bound__) # revealed: None +``` + +### Type variables with constraints + +```py +def f[T: (int, str)](): + reveal_type(T.__constraints__) # revealed: tuple[int, str] + reveal_type(T.__bound__) # revealed: None + +def g[S](): + reveal_type(S.__constraints__) # revealed: tuple[()] +``` + ### Cannot have only one constraint > `TypeVar` supports constraining parametric types to a fixed set of possible types...There should diff --git a/crates/red_knot_python_semantic/resources/mdtest/generics/scoping.md b/crates/red_knot_python_semantic/resources/mdtest/generics/scoping.md index 9712be7213f41c..0300cadc87d9af 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/generics/scoping.md +++ b/crates/red_knot_python_semantic/resources/mdtest/generics/scoping.md @@ -142,8 +142,7 @@ class Legacy(Generic[T]): return y legacy: Legacy[int] = Legacy() -# TODO: revealed: str -reveal_type(legacy.m(1, "string")) # revealed: @Todo(Support for `typing.TypeVar` instances in type expressions) +reveal_type(legacy.m(1, "string")) # revealed: Literal["string"] ``` With PEP 695 syntax, it is clearer that the method uses a separate typevar: diff --git a/crates/red_knot_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type.snap b/crates/red_knot_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type.snap index 67a88e4dc8d335..7e439527f1e84d 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type.snap +++ b/crates/red_knot_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type.snap @@ -28,7 +28,7 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/function/return_ty 14 | 15 | T = TypeVar("T") 16 | -17 | # TODO: `invalid-return-type` error should be emitted +17 | # error: [invalid-return-type] 18 | def m(x: T) -> T: ... ``` @@ -79,3 +79,14 @@ error: lint:invalid-return-type: Return type does not match returned value | ``` + +``` +error: lint:invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `T` + --> src/mdtest_snippet.py:18:16 + | +17 | # error: [invalid-return-type] +18 | def m(x: T) -> T: ... + | ^ + | + +``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/subscript/lists.md b/crates/red_knot_python_semantic/resources/mdtest/subscript/lists.md index 192dc4a88e10e0..d074d1b82669a8 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/subscript/lists.md +++ b/crates/red_knot_python_semantic/resources/mdtest/subscript/lists.md @@ -12,7 +12,7 @@ x = [1, 2, 3] reveal_type(x) # revealed: list # TODO reveal int -reveal_type(x[0]) # revealed: @Todo(Support for `typing.TypeVar` instances in type expressions) +reveal_type(x[0]) # revealed: Unknown # TODO reveal list reveal_type(x[0:1]) # revealed: @Todo(specialized non-generic class) diff --git a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_assignable_to.md b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_assignable_to.md index ce7198ba697e12..f8b8adf5f8b832 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_assignable_to.md +++ b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_assignable_to.md @@ -583,6 +583,8 @@ from functools import partial def f(x: int, y: str) -> None: ... +# TODO: no error +# error: [invalid-assignment] "Object of type `partial` is not assignable to `(int, /) -> None`" c1: Callable[[int], None] = partial(f, y="a") ``` 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 a17e4523f360a4..22af0fda06d8c2 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder.rs @@ -754,19 +754,35 @@ impl<'db> SemanticIndexBuilder<'db> { /// Record an expression that needs to be a Salsa ingredient, because we need to infer its type /// standalone (type narrowing tests, RHS of an assignment.) fn add_standalone_expression(&mut self, expression_node: &ast::Expr) -> Expression<'db> { - self.add_standalone_expression_impl(expression_node, ExpressionKind::Normal) + self.add_standalone_expression_impl(expression_node, ExpressionKind::Normal, None) + } + + /// Record an expression that is immediately assigned to a target, and that needs to be a Salsa + /// ingredient, because we need to infer its type standalone (type narrowing tests, RHS of an + /// assignment.) + fn add_standalone_assigned_expression( + &mut self, + expression_node: &ast::Expr, + assigned_to: &ast::StmtAssign, + ) -> Expression<'db> { + self.add_standalone_expression_impl( + expression_node, + ExpressionKind::Normal, + Some(assigned_to), + ) } /// Same as [`SemanticIndexBuilder::add_standalone_expression`], but marks the expression as a /// *type* expression, which makes sure that it will later be inferred as such. fn add_standalone_type_expression(&mut self, expression_node: &ast::Expr) -> Expression<'db> { - self.add_standalone_expression_impl(expression_node, ExpressionKind::TypeExpression) + self.add_standalone_expression_impl(expression_node, ExpressionKind::TypeExpression, None) } fn add_standalone_expression_impl( &mut self, expression_node: &ast::Expr, expression_kind: ExpressionKind, + assigned_to: Option<&ast::StmtAssign>, ) -> Expression<'db> { let expression = Expression::new( self.db, @@ -776,6 +792,9 @@ impl<'db> SemanticIndexBuilder<'db> { unsafe { AstNodeRef::new(self.module.clone(), expression_node) }, + #[allow(unsafe_code)] + assigned_to + .map(|assigned_to| unsafe { AstNodeRef::new(self.module.clone(), assigned_to) }), expression_kind, countme::Count::default(), ); @@ -1377,7 +1396,7 @@ where debug_assert_eq!(&self.current_assignments, &[]); self.visit_expr(&node.value); - let value = self.add_standalone_expression(&node.value); + let value = self.add_standalone_assigned_expression(&node.value, node); for target in &node.targets { self.add_unpackable_assignment(&Unpackable::Assign(node), target, value); diff --git a/crates/red_knot_python_semantic/src/semantic_index/expression.rs b/crates/red_knot_python_semantic/src/semantic_index/expression.rs index 9ac1fd30b81eb7..8c50ea5bb9c917 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/expression.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/expression.rs @@ -44,6 +44,17 @@ pub(crate) struct Expression<'db> { #[return_ref] pub(crate) node_ref: AstNodeRef, + /// An assignment statement, if this expression is immediately used as the rhs of that + /// assignment. + /// + /// (Note that this is the _immediately_ containing assignment — if a complex expression is + /// assigned to some target, only the outermost expression node has this set. The inner + /// expressions are used to build up the assignment result, and are not "immediately assigned" + /// to the target, and so have `None` for this field.) + #[no_eq] + #[tracked] + pub(crate) assigned_to: Option>, + /// Should this expression be inferred as a normal expression or a type expression? pub(crate) kind: ExpressionKind, diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 1c2e15a3cd9aef..45ec9e91cd4610 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -348,6 +348,19 @@ impl<'db> PropertyInstanceType<'db> { .map(|ty| ty.apply_specialization(db, specialization)); Self::new(db, getter, setter) } + + fn find_legacy_typevars( + self, + db: &'db dyn Db, + typevars: &mut FxOrderSet>, + ) { + if let Some(ty) = self.getter(db) { + ty.find_legacy_typevars(db, typevars); + } + if let Some(ty) = self.setter(db) { + ty.find_legacy_typevars(db, typevars); + } + } } bitflags! { @@ -923,6 +936,7 @@ impl<'db> Type<'db> { typevar.definition(db), Some(TypeVarBoundOrConstraints::UpperBound(bound.normalized(db))), typevar.default_ty(db), + typevar.kind(db), )) } Some(TypeVarBoundOrConstraints::Constraints(union)) => { @@ -932,6 +946,7 @@ impl<'db> Type<'db> { typevar.definition(db), Some(TypeVarBoundOrConstraints::Constraints(union.normalized(db))), typevar.default_ty(db), + typevar.kind(db), )) } None => self, @@ -3799,6 +3814,56 @@ impl<'db> Type<'db> { Signatures::single(signature) } + Some(KnownClass::TypeVar) => { + // ```py + // class TypeVar: + // def __new__( + // cls, + // name: str, + // *constraints: Any, + // bound: Any | None = None, + // contravariant: bool = False, + // covariant: bool = False, + // infer_variance: bool = False, + // default: Any = ..., + // ) -> Self: ... + // ``` + let signature = CallableSignature::single( + self, + Signature::new( + Parameters::new([ + Parameter::positional_or_keyword(Name::new_static("name")) + .with_annotated_type(Type::LiteralString), + Parameter::variadic(Name::new_static("constraints")) + .type_form() + .with_annotated_type(Type::any()), + Parameter::keyword_only(Name::new_static("bound")) + .type_form() + .with_annotated_type(UnionType::from_elements( + db, + [Type::any(), Type::none(db)], + )) + .with_default_type(Type::none(db)), + Parameter::keyword_only(Name::new_static("default")) + .type_form() + .with_annotated_type(Type::any()) + .with_default_type(KnownClass::NoneType.to_instance(db)), + Parameter::keyword_only(Name::new_static("contravariant")) + .with_annotated_type(KnownClass::Bool.to_instance(db)) + .with_default_type(Type::BooleanLiteral(false)), + Parameter::keyword_only(Name::new_static("covariant")) + .with_annotated_type(KnownClass::Bool.to_instance(db)) + .with_default_type(Type::BooleanLiteral(false)), + Parameter::keyword_only(Name::new_static("infer_variance")) + .with_annotated_type(KnownClass::Bool.to_instance(db)) + .with_default_type(Type::BooleanLiteral(false)), + ]), + Some(KnownClass::TypeVar.to_instance(db)), + ), + ); + Signatures::single(signature) + } + Some(KnownClass::Property) => { let getter_signature = Signature::new( Parameters::new([ @@ -4306,27 +4371,43 @@ impl<'db> Type<'db> { new_call_outcome @ (None | Some(Ok(_))), init_call_outcome @ (None | Some(Ok(_))), ) => { + fn combine_specializations<'db>( + db: &'db dyn Db, + s1: Option>, + s2: Option>, + ) -> Option> { + match (s1, s2) { + (None, None) => None, + (Some(s), None) | (None, Some(s)) => Some(s), + (Some(s1), Some(s2)) => Some(s1.combine(db, s2)), + } + } + + fn combine_binding_specialization<'db>( + db: &'db dyn Db, + binding: &CallableBinding<'db>, + ) -> Option> { + binding + .matching_overloads() + .map(|(_, binding)| binding.inherited_specialization()) + .reduce(|acc, specialization| { + combine_specializations(db, acc, specialization) + }) + .flatten() + } + let new_specialization = new_call_outcome .and_then(Result::ok) .as_ref() .and_then(Bindings::single_element) - .and_then(CallableBinding::matching_overload) - .and_then(|(_, binding)| binding.inherited_specialization()); + .and_then(|binding| combine_binding_specialization(db, binding)); let init_specialization = init_call_outcome .and_then(Result::ok) .as_ref() .and_then(Bindings::single_element) - .and_then(CallableBinding::matching_overload) - .and_then(|(_, binding)| binding.inherited_specialization()); - let specialization = match (new_specialization, init_specialization) { - (None, None) => None, - (Some(specialization), None) | (None, Some(specialization)) => { - Some(specialization) - } - (Some(new_specialization), Some(init_specialization)) => { - Some(new_specialization.combine(db, init_specialization)) - } - }; + .and_then(|binding| combine_binding_specialization(db, binding)); + let specialization = + combine_specializations(db, new_specialization, init_specialization); let specialized = specialization .map(|specialization| { Type::instance(ClassType::Generic(GenericAlias::new( @@ -4834,6 +4915,93 @@ impl<'db> Type<'db> { } } + /// Locates any legacy `TypeVar`s in this type, and adds them to a set. This is used to build + /// up a generic context from any legacy `TypeVar`s that appear in a function parameter list or + /// `Generic` specialization. + pub(crate) fn find_legacy_typevars( + self, + db: &'db dyn Db, + typevars: &mut FxOrderSet>, + ) { + match self { + Type::TypeVar(typevar) => { + if typevar.is_legacy(db) { + typevars.insert(typevar); + } + } + + Type::FunctionLiteral(function) => function.find_legacy_typevars(db, typevars), + + Type::BoundMethod(method) => { + method.self_instance(db).find_legacy_typevars(db, typevars); + method.function(db).find_legacy_typevars(db, typevars); + } + + Type::MethodWrapper( + MethodWrapperKind::FunctionTypeDunderGet(function) + | MethodWrapperKind::FunctionTypeDunderCall(function), + ) => { + function.find_legacy_typevars(db, typevars); + } + + Type::MethodWrapper( + MethodWrapperKind::PropertyDunderGet(property) + | MethodWrapperKind::PropertyDunderSet(property), + ) => { + property.find_legacy_typevars(db, typevars); + } + + Type::Callable(callable) => { + callable.find_legacy_typevars(db, typevars); + } + + Type::PropertyInstance(property) => { + property.find_legacy_typevars(db, typevars); + } + + Type::Union(union) => { + for element in union.iter(db) { + element.find_legacy_typevars(db, typevars); + } + } + Type::Intersection(intersection) => { + for positive in intersection.positive(db) { + positive.find_legacy_typevars(db, typevars); + } + for negative in intersection.negative(db) { + negative.find_legacy_typevars(db, typevars); + } + } + Type::Tuple(tuple) => { + for element in tuple.iter(db) { + element.find_legacy_typevars(db, typevars); + } + } + + Type::Dynamic(_) + | Type::Never + | Type::AlwaysTruthy + | Type::AlwaysFalsy + | Type::WrapperDescriptor(_) + | Type::MethodWrapper(MethodWrapperKind::StrStartswith(_)) + | Type::DataclassDecorator(_) + | Type::DataclassTransformer(_) + | Type::ModuleLiteral(_) + | Type::ClassLiteral(_) + | Type::GenericAlias(_) + | Type::SubclassOf(_) + | Type::IntLiteral(_) + | Type::BooleanLiteral(_) + | Type::LiteralString + | Type::StringLiteral(_) + | Type::BytesLiteral(_) + | Type::SliceLiteral(_) + | Type::BoundSuper(_) + | Type::Instance(_) + | Type::KnownInstance(_) => {} + } + } + /// Return the string representation of this type when converted to string as it would be /// provided by the `__str__` method. /// @@ -4844,9 +5012,7 @@ impl<'db> Type<'db> { match self { Type::IntLiteral(_) | Type::BooleanLiteral(_) => self.repr(db), Type::StringLiteral(_) | Type::LiteralString => *self, - Type::KnownInstance(known_instance) => { - Type::string_literal(db, known_instance.repr(db)) - } + Type::KnownInstance(known_instance) => Type::string_literal(db, known_instance.repr()), // TODO: handle more complex types _ => KnownClass::Str.to_instance(db), } @@ -4864,9 +5030,7 @@ impl<'db> Type<'db> { Type::string_literal(db, &format!("'{}'", literal.value(db).escape_default())) } Type::LiteralString => Type::LiteralString, - Type::KnownInstance(known_instance) => { - Type::string_literal(db, known_instance.repr(db)) - } + Type::KnownInstance(known_instance) => Type::string_literal(db, known_instance.repr()), // TODO: handle more complex types _ => KnownClass::Str.to_instance(db), } @@ -5235,12 +5399,12 @@ impl<'db> InvalidTypeExpression<'db> { InvalidTypeExpression::TypeQualifier(qualifier) => write!( f, "Type qualifier `{q}` is not allowed in type expressions (only in annotation expressions)", - q = qualifier.repr(self.db) + q = qualifier.repr() ), InvalidTypeExpression::TypeQualifierRequiresOneArgument(qualifier) => write!( f, "Type qualifier `{q}` is not allowed in type expressions (only in annotation expressions, and only with exactly one argument)", - q = qualifier.repr(self.db) + q = qualifier.repr() ), InvalidTypeExpression::InvalidType(ty) => write!( f, @@ -5255,6 +5419,13 @@ impl<'db> InvalidTypeExpression<'db> { } } +/// Whether this typecar was created via the legacy `TypeVar` constructor, or using PEP 695 syntax. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum TypeVarKind { + Legacy, + Pep695, +} + /// Data regarding a single type variable. /// /// This is referenced by `KnownInstanceType::TypeVar` (to represent the singleton type of the @@ -5276,9 +5447,15 @@ pub struct TypeVarInstance<'db> { /// The default type for this TypeVar default_ty: Option>, + + pub kind: TypeVarKind, } impl<'db> TypeVarInstance<'db> { + pub(crate) fn is_legacy(self, db: &'db dyn Db) -> bool { + matches!(self.kind(db), TypeVarKind::Legacy) + } + #[allow(unused)] pub(crate) fn upper_bound(self, db: &'db dyn Db) -> Option> { if let Some(TypeVarBoundOrConstraints::UpperBound(ty)) = self.bound_or_constraints(db) { @@ -6368,6 +6545,17 @@ impl<'db> FunctionType<'db> { ) } + fn find_legacy_typevars( + self, + db: &'db dyn Db, + typevars: &mut FxOrderSet>, + ) { + let signatures = self.signature(db); + for signature in signatures { + signature.find_legacy_typevars(db, typevars); + } + } + /// Returns `self` as [`OverloadedFunction`] if it is overloaded, [`None`] otherwise. /// /// ## Note @@ -6698,6 +6886,16 @@ impl<'db> CallableType<'db> { ) } + fn find_legacy_typevars( + self, + db: &'db dyn Db, + typevars: &mut FxOrderSet>, + ) { + for signature in self.signatures(db) { + signature.find_legacy_typevars(db, typevars); + } + } + /// Check whether this callable type is fully static. /// /// See [`Type::is_fully_static`] for more details. diff --git a/crates/red_knot_python_semantic/src/types/call/bind.rs b/crates/red_knot_python_semantic/src/types/call/bind.rs index 8f5fb666589fd0..d2ce97e0fb99ac 100644 --- a/crates/red_knot_python_semantic/src/types/call/bind.rs +++ b/crates/red_knot_python_semantic/src/types/call/bind.rs @@ -20,8 +20,8 @@ use crate::types::generics::{Specialization, SpecializationBuilder}; use crate::types::signatures::{Parameter, ParameterForm}; use crate::types::{ todo_type, BoundMethodType, DataclassParams, DataclassTransformerParams, FunctionDecorators, - KnownClass, KnownFunction, KnownInstanceType, MethodWrapperKind, PropertyInstanceType, - TupleType, UnionType, WrapperDescriptorKind, + IntersectionBuilder, KnownClass, KnownFunction, KnownInstanceType, MethodWrapperKind, + PropertyInstanceType, TupleType, UnionType, WrapperDescriptorKind, }; use ruff_db::diagnostic::{Annotation, Severity, SubDiagnostic}; use ruff_python_ast as ast; @@ -164,9 +164,9 @@ impl<'db> Bindings<'db> { /// types that are not callable, returns `Type::Unknown`. pub(crate) fn return_type(&self, db: &'db dyn Db) -> Type<'db> { if let [binding] = self.elements.as_slice() { - return binding.return_type(); + return binding.return_type(db); } - UnionType::from_elements(db, self.into_iter().map(CallableBinding::return_type)) + UnionType::from_elements(db, self.into_iter().map(|binding| binding.return_type(db))) } /// Report diagnostics for all of the errors that occurred when trying to match actual @@ -221,563 +221,599 @@ impl<'db> Bindings<'db> { // Each special case listed here should have a corresponding clause in `Type::signatures`. for (binding, callable_signature) in self.elements.iter_mut().zip(self.signatures.iter()) { let binding_type = binding.callable_type; - let Some((overload_index, overload)) = binding.matching_overload_mut() else { - continue; - }; - - match binding_type { - Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderGet(function)) => { - if function.has_known_decorator(db, FunctionDecorators::CLASSMETHOD) { - match overload.parameter_types() { - [_, Some(owner)] => { - overload.set_return_type(Type::BoundMethod(BoundMethodType::new( - db, function, *owner, - ))); - } - [Some(instance), None] => { - overload.set_return_type(Type::BoundMethod(BoundMethodType::new( - db, - function, - instance.to_meta_type(db), - ))); - } - _ => {} - } - } else if let [Some(first), _] = overload.parameter_types() { - if first.is_none(db) { - overload.set_return_type(Type::FunctionLiteral(function)); - } else { - overload.set_return_type(Type::BoundMethod(BoundMethodType::new( - db, function, *first, - ))); - } - } - } - - Type::WrapperDescriptor(WrapperDescriptorKind::FunctionTypeDunderGet) => { - if let [Some(function_ty @ Type::FunctionLiteral(function)), ..] = - overload.parameter_types() - { + for (overload_index, overload) in binding.matching_overloads_mut() { + match binding_type { + Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderGet(function)) => { if function.has_known_decorator(db, FunctionDecorators::CLASSMETHOD) { match overload.parameter_types() { - [_, _, Some(owner)] => { + [_, Some(owner)] => { overload.set_return_type(Type::BoundMethod( - BoundMethodType::new(db, *function, *owner), + BoundMethodType::new(db, function, *owner), )); } - - [_, Some(instance), None] => { + [Some(instance), None] => { overload.set_return_type(Type::BoundMethod( BoundMethodType::new( db, - *function, + function, instance.to_meta_type(db), ), )); } - _ => {} } - } else { - match overload.parameter_types() { - [_, Some(instance), _] if instance.is_none(db) => { - overload.set_return_type(*function_ty); - } - [_, Some(instance), _] => { - overload.set_return_type(Type::BoundMethod( - BoundMethodType::new(db, *function, *instance), - )); - } - - _ => {} + } else if let [Some(first), _] = overload.parameter_types() { + if first.is_none(db) { + overload.set_return_type(Type::FunctionLiteral(function)); + } else { + overload.set_return_type(Type::BoundMethod(BoundMethodType::new( + db, function, *first, + ))); } } } - } - Type::WrapperDescriptor(WrapperDescriptorKind::PropertyDunderGet) => { - match overload.parameter_types() { - [Some(property @ Type::PropertyInstance(_)), Some(instance), ..] - if instance.is_none(db) => - { - overload.set_return_type(*property); - } - [Some(Type::PropertyInstance(property)), Some(Type::KnownInstance(KnownInstanceType::TypeAliasType(type_alias))), ..] - if property.getter(db).is_some_and(|getter| { - getter - .into_function_literal() - .is_some_and(|f| f.name(db) == "__name__") - }) => - { - overload.set_return_type(Type::string_literal(db, type_alias.name(db))); - } - [Some(Type::PropertyInstance(property)), Some(Type::KnownInstance(KnownInstanceType::TypeVar(type_var))), ..] - if property.getter(db).is_some_and(|getter| { - getter - .into_function_literal() - .is_some_and(|f| f.name(db) == "__name__") - }) => + Type::WrapperDescriptor(WrapperDescriptorKind::FunctionTypeDunderGet) => { + if let [Some(function_ty @ Type::FunctionLiteral(function)), ..] = + overload.parameter_types() { - overload.set_return_type(Type::string_literal(db, type_var.name(db))); + if function.has_known_decorator(db, FunctionDecorators::CLASSMETHOD) { + match overload.parameter_types() { + [_, _, Some(owner)] => { + overload.set_return_type(Type::BoundMethod( + BoundMethodType::new(db, *function, *owner), + )); + } + + [_, Some(instance), None] => { + overload.set_return_type(Type::BoundMethod( + BoundMethodType::new( + db, + *function, + instance.to_meta_type(db), + ), + )); + } + + _ => {} + } + } else { + match overload.parameter_types() { + [_, Some(instance), _] if instance.is_none(db) => { + overload.set_return_type(*function_ty); + } + [_, Some(instance), _] => { + overload.set_return_type(Type::BoundMethod( + BoundMethodType::new(db, *function, *instance), + )); + } + + _ => {} + } + } } - [Some(Type::PropertyInstance(property)), Some(instance), ..] => { - if let Some(getter) = property.getter(db) { - if let Ok(return_ty) = getter - .try_call(db, CallArgumentTypes::positional([*instance])) - .map(|binding| binding.return_type(db)) + } + + Type::WrapperDescriptor(WrapperDescriptorKind::PropertyDunderGet) => { + match overload.parameter_types() { + [Some(property @ Type::PropertyInstance(_)), Some(instance), ..] + if instance.is_none(db) => + { + overload.set_return_type(*property); + } + [Some(Type::PropertyInstance(property)), Some(Type::KnownInstance(KnownInstanceType::TypeAliasType( + type_alias, + ))), ..] + if property.getter(db).is_some_and(|getter| { + getter + .into_function_literal() + .is_some_and(|f| f.name(db) == "__name__") + }) => + { + overload + .set_return_type(Type::string_literal(db, type_alias.name(db))); + } + [Some(Type::PropertyInstance(property)), Some(Type::KnownInstance(KnownInstanceType::TypeVar(typevar))), ..] => { + match property + .getter(db) + .and_then(Type::into_function_literal) + .map(|f| f.name(db).as_str()) { - overload.set_return_type(return_ty); + Some("__name__") => { + overload.set_return_type(Type::string_literal( + db, + typevar.name(db), + )); + } + Some("__bound__") => { + overload.set_return_type( + typevar + .upper_bound(db) + .unwrap_or_else(|| Type::none(db)), + ); + } + Some("__constraints__") => { + overload.set_return_type(TupleType::from_elements( + db, + typevar.constraints(db).into_iter().flatten(), + )); + } + Some("__default__") => { + overload.set_return_type( + typevar.default_ty(db).unwrap_or_else(|| { + KnownClass::NoDefaultType.to_instance(db) + }), + ); + } + _ => {} + } + } + [Some(Type::PropertyInstance(property)), Some(instance), ..] => { + if let Some(getter) = property.getter(db) { + if let Ok(return_ty) = getter + .try_call(db, CallArgumentTypes::positional([*instance])) + .map(|binding| binding.return_type(db)) + { + overload.set_return_type(return_ty); + } else { + overload.errors.push(BindingError::InternalCallError( + "calling the getter failed", + )); + overload.set_return_type(Type::unknown()); + } } else { overload.errors.push(BindingError::InternalCallError( - "calling the getter failed", + "property has no getter", )); - overload.set_return_type(Type::unknown()); + overload.set_return_type(Type::Never); } - } else { - overload.errors.push(BindingError::InternalCallError( - "property has no getter", - )); - overload.set_return_type(Type::Never); } + _ => {} } - _ => {} } - } - Type::MethodWrapper(MethodWrapperKind::PropertyDunderGet(property)) => { - match overload.parameter_types() { - [Some(instance), ..] if instance.is_none(db) => { - overload.set_return_type(Type::PropertyInstance(property)); - } - [Some(instance), ..] => { - if let Some(getter) = property.getter(db) { - if let Ok(return_ty) = getter - .try_call(db, CallArgumentTypes::positional([*instance])) - .map(|binding| binding.return_type(db)) - { - overload.set_return_type(return_ty); + Type::MethodWrapper(MethodWrapperKind::PropertyDunderGet(property)) => { + match overload.parameter_types() { + [Some(instance), ..] if instance.is_none(db) => { + overload.set_return_type(Type::PropertyInstance(property)); + } + [Some(instance), ..] => { + if let Some(getter) = property.getter(db) { + if let Ok(return_ty) = getter + .try_call(db, CallArgumentTypes::positional([*instance])) + .map(|binding| binding.return_type(db)) + { + overload.set_return_type(return_ty); + } else { + overload.errors.push(BindingError::InternalCallError( + "calling the getter failed", + )); + overload.set_return_type(Type::unknown()); + } } else { + overload.set_return_type(Type::Never); overload.errors.push(BindingError::InternalCallError( - "calling the getter failed", + "property has no getter", )); - overload.set_return_type(Type::unknown()); } - } else { - overload.set_return_type(Type::Never); - overload.errors.push(BindingError::InternalCallError( - "property has no getter", - )); } + _ => {} } - _ => {} } - } - Type::WrapperDescriptor(WrapperDescriptorKind::PropertyDunderSet) => { - if let [Some(Type::PropertyInstance(property)), Some(instance), Some(value), ..] = - overload.parameter_types() - { - if let Some(setter) = property.setter(db) { - if let Err(_call_error) = setter - .try_call(db, CallArgumentTypes::positional([*instance, *value])) - { + Type::WrapperDescriptor(WrapperDescriptorKind::PropertyDunderSet) => { + if let [Some(Type::PropertyInstance(property)), Some(instance), Some(value), ..] = + overload.parameter_types() + { + if let Some(setter) = property.setter(db) { + if let Err(_call_error) = setter.try_call( + db, + CallArgumentTypes::positional([*instance, *value]), + ) { + overload.errors.push(BindingError::InternalCallError( + "calling the setter failed", + )); + } + } else { overload.errors.push(BindingError::InternalCallError( - "calling the setter failed", + "property has no setter", )); } - } else { - overload - .errors - .push(BindingError::InternalCallError("property has no setter")); } } - } - Type::MethodWrapper(MethodWrapperKind::PropertyDunderSet(property)) => { - if let [Some(instance), Some(value), ..] = overload.parameter_types() { - if let Some(setter) = property.setter(db) { - if let Err(_call_error) = setter - .try_call(db, CallArgumentTypes::positional([*instance, *value])) - { + Type::MethodWrapper(MethodWrapperKind::PropertyDunderSet(property)) => { + if let [Some(instance), Some(value), ..] = overload.parameter_types() { + if let Some(setter) = property.setter(db) { + if let Err(_call_error) = setter.try_call( + db, + CallArgumentTypes::positional([*instance, *value]), + ) { + overload.errors.push(BindingError::InternalCallError( + "calling the setter failed", + )); + } + } else { overload.errors.push(BindingError::InternalCallError( - "calling the setter failed", + "property has no setter", )); } - } else { - overload - .errors - .push(BindingError::InternalCallError("property has no setter")); } } - } - Type::MethodWrapper(MethodWrapperKind::StrStartswith(literal)) => { - if let [Some(Type::StringLiteral(prefix)), None, None] = - overload.parameter_types() - { - overload.set_return_type(Type::BooleanLiteral( - literal.value(db).starts_with(&**prefix.value(db)), - )); + Type::MethodWrapper(MethodWrapperKind::StrStartswith(literal)) => { + if let [Some(Type::StringLiteral(prefix)), None, None] = + overload.parameter_types() + { + overload.set_return_type(Type::BooleanLiteral( + literal.value(db).starts_with(&**prefix.value(db)), + )); + } } - } - Type::DataclassTransformer(params) => { - if let [Some(Type::FunctionLiteral(function))] = overload.parameter_types() { - overload.set_return_type(Type::FunctionLiteral( - function.with_dataclass_transformer_params(db, params), - )); + Type::DataclassTransformer(params) => { + if let [Some(Type::FunctionLiteral(function))] = overload.parameter_types() + { + overload.set_return_type(Type::FunctionLiteral( + function.with_dataclass_transformer_params(db, params), + )); + } } - } - Type::BoundMethod(bound_method) - if bound_method.self_instance(db).is_property_instance() => - { - match bound_method.function(db).name(db).as_str() { - "setter" => { - if let [Some(_), Some(setter)] = overload.parameter_types() { - let mut ty_property = bound_method.self_instance(db); - if let Type::PropertyInstance(property) = ty_property { - ty_property = - Type::PropertyInstance(PropertyInstanceType::new( - db, - property.getter(db), - Some(*setter), - )); + Type::BoundMethod(bound_method) + if bound_method.self_instance(db).is_property_instance() => + { + match bound_method.function(db).name(db).as_str() { + "setter" => { + if let [Some(_), Some(setter)] = overload.parameter_types() { + let mut ty_property = bound_method.self_instance(db); + if let Type::PropertyInstance(property) = ty_property { + ty_property = + Type::PropertyInstance(PropertyInstanceType::new( + db, + property.getter(db), + Some(*setter), + )); + } + overload.set_return_type(ty_property); } - overload.set_return_type(ty_property); } - } - "getter" => { - if let [Some(_), Some(getter)] = overload.parameter_types() { - let mut ty_property = bound_method.self_instance(db); - if let Type::PropertyInstance(property) = ty_property { - ty_property = - Type::PropertyInstance(PropertyInstanceType::new( - db, - Some(*getter), - property.setter(db), - )); + "getter" => { + if let [Some(_), Some(getter)] = overload.parameter_types() { + let mut ty_property = bound_method.self_instance(db); + if let Type::PropertyInstance(property) = ty_property { + ty_property = + Type::PropertyInstance(PropertyInstanceType::new( + db, + Some(*getter), + property.setter(db), + )); + } + overload.set_return_type(ty_property); } + } + "deleter" => { + // TODO: we do not store deleters yet + let ty_property = bound_method.self_instance(db); overload.set_return_type(ty_property); } - } - "deleter" => { - // TODO: we do not store deleters yet - let ty_property = bound_method.self_instance(db); - overload.set_return_type(ty_property); - } - _ => { - // Fall back to typeshed stubs for all other methods + _ => { + // Fall back to typeshed stubs for all other methods + } } } - } - Type::FunctionLiteral(function_type) => match function_type.known(db) { - Some(KnownFunction::IsEquivalentTo) => { - if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral( - ty_a.is_equivalent_to(db, *ty_b), - )); + Type::FunctionLiteral(function_type) => match function_type.known(db) { + Some(KnownFunction::IsEquivalentTo) => { + if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral( + ty_a.is_equivalent_to(db, *ty_b), + )); + } } - } - Some(KnownFunction::IsSubtypeOf) => { - if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral( - ty_a.is_subtype_of(db, *ty_b), - )); + Some(KnownFunction::IsSubtypeOf) => { + if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral( + ty_a.is_subtype_of(db, *ty_b), + )); + } } - } - Some(KnownFunction::IsAssignableTo) => { - if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral( - ty_a.is_assignable_to(db, *ty_b), - )); + Some(KnownFunction::IsAssignableTo) => { + if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral( + ty_a.is_assignable_to(db, *ty_b), + )); + } } - } - Some(KnownFunction::IsDisjointFrom) => { - if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral( - ty_a.is_disjoint_from(db, *ty_b), - )); + Some(KnownFunction::IsDisjointFrom) => { + if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral( + ty_a.is_disjoint_from(db, *ty_b), + )); + } } - } - Some(KnownFunction::IsGradualEquivalentTo) => { - if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral( - ty_a.is_gradual_equivalent_to(db, *ty_b), - )); + Some(KnownFunction::IsGradualEquivalentTo) => { + if let [Some(ty_a), Some(ty_b)] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral( + ty_a.is_gradual_equivalent_to(db, *ty_b), + )); + } } - } - Some(KnownFunction::IsFullyStatic) => { - if let [Some(ty)] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral(ty.is_fully_static(db))); + Some(KnownFunction::IsFullyStatic) => { + if let [Some(ty)] = overload.parameter_types() { + overload + .set_return_type(Type::BooleanLiteral(ty.is_fully_static(db))); + } } - } - Some(KnownFunction::IsSingleton) => { - if let [Some(ty)] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral(ty.is_singleton(db))); + Some(KnownFunction::IsSingleton) => { + if let [Some(ty)] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral(ty.is_singleton(db))); + } } - } - Some(KnownFunction::IsSingleValued) => { - if let [Some(ty)] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral(ty.is_single_valued(db))); + Some(KnownFunction::IsSingleValued) => { + if let [Some(ty)] = overload.parameter_types() { + overload + .set_return_type(Type::BooleanLiteral(ty.is_single_valued(db))); + } } - } - Some(KnownFunction::Len) => { - if let [Some(first_arg)] = overload.parameter_types() { - if let Some(len_ty) = first_arg.len(db) { - overload.set_return_type(len_ty); + Some(KnownFunction::Len) => { + if let [Some(first_arg)] = overload.parameter_types() { + if let Some(len_ty) = first_arg.len(db) { + overload.set_return_type(len_ty); + } } } - } - Some(KnownFunction::Repr) => { - if let [Some(first_arg)] = overload.parameter_types() { - overload.set_return_type(first_arg.repr(db)); + Some(KnownFunction::Repr) => { + if let [Some(first_arg)] = overload.parameter_types() { + overload.set_return_type(first_arg.repr(db)); + } } - } - Some(KnownFunction::Cast) => { - if let [Some(casted_ty), Some(_)] = overload.parameter_types() { - overload.set_return_type(*casted_ty); + Some(KnownFunction::Cast) => { + if let [Some(casted_ty), Some(_)] = overload.parameter_types() { + overload.set_return_type(*casted_ty); + } } - } - Some(KnownFunction::IsProtocol) => { - if let [Some(ty)] = overload.parameter_types() { - overload.set_return_type(Type::BooleanLiteral( - ty.into_class_literal() - .is_some_and(|class| class.is_protocol(db)), - )); + Some(KnownFunction::IsProtocol) => { + if let [Some(ty)] = overload.parameter_types() { + overload.set_return_type(Type::BooleanLiteral( + ty.into_class_literal() + .is_some_and(|class| class.is_protocol(db)), + )); + } } - } - Some(KnownFunction::GetProtocolMembers) => { - if let [Some(Type::ClassLiteral(class))] = overload.parameter_types() { - if let Some(protocol_class) = class.into_protocol_class(db) { - // TODO: actually a frozenset at runtime (requires support for legacy generic classes) - overload.set_return_type(Type::Tuple(TupleType::new( - db, - protocol_class - .protocol_members(db) - .iter() - .map(|member| Type::string_literal(db, member)) - .collect::]>>(), - ))); + Some(KnownFunction::GetProtocolMembers) => { + if let [Some(Type::ClassLiteral(class))] = overload.parameter_types() { + if let Some(protocol_class) = class.into_protocol_class(db) { + // TODO: actually a frozenset at runtime (requires support for legacy generic classes) + overload.set_return_type(Type::Tuple(TupleType::new( + db, + protocol_class + .protocol_members(db) + .iter() + .map(|member| Type::string_literal(db, member)) + .collect::]>>(), + ))); + } } } - } - Some(KnownFunction::Overload) => { - // TODO: This can be removed once we understand legacy generics because the - // typeshed definition for `typing.overload` is an identity function. - if let [Some(ty)] = overload.parameter_types() { - overload.set_return_type(*ty); + Some(KnownFunction::Overload) => { + // TODO: This can be removed once we understand legacy generics because the + // typeshed definition for `typing.overload` is an identity function. + if let [Some(ty)] = overload.parameter_types() { + overload.set_return_type(*ty); + } } - } - Some(KnownFunction::Override) => { - // TODO: This can be removed once we understand legacy generics because the - // typeshed definition for `typing.overload` is an identity function. - if let [Some(ty)] = overload.parameter_types() { - overload.set_return_type(*ty); + Some(KnownFunction::Override) => { + // TODO: This can be removed once we understand legacy generics because the + // typeshed definition for `typing.overload` is an identity function. + if let [Some(ty)] = overload.parameter_types() { + overload.set_return_type(*ty); + } } - } - Some(KnownFunction::AbstractMethod) => { - // TODO: This can be removed once we understand legacy generics because the - // typeshed definition for `abc.abstractmethod` is an identity function. - if let [Some(ty)] = overload.parameter_types() { - overload.set_return_type(*ty); + Some(KnownFunction::AbstractMethod) => { + // TODO: This can be removed once we understand legacy generics because the + // typeshed definition for `abc.abstractmethod` is an identity function. + if let [Some(ty)] = overload.parameter_types() { + overload.set_return_type(*ty); + } } - } - Some(KnownFunction::Final) => { - // TODO: This can be removed once we understand legacy generics because the - // typeshed definition for `abc.abstractmethod` is an identity function. - if let [Some(ty)] = overload.parameter_types() { - overload.set_return_type(*ty); + Some(KnownFunction::Final) => { + // TODO: This can be removed once we understand legacy generics because the + // typeshed definition for `abc.abstractmethod` is an identity function. + if let [Some(ty)] = overload.parameter_types() { + overload.set_return_type(*ty); + } } - } - - Some(KnownFunction::GetattrStatic) => { - let [Some(instance_ty), Some(attr_name), default] = - overload.parameter_types() - else { - continue; - }; - let Some(attr_name) = attr_name.into_string_literal() else { - continue; - }; - - let default = if let Some(default) = default { - *default - } else { - Type::Never - }; + Some(KnownFunction::GetattrStatic) => { + let [Some(instance_ty), Some(attr_name), default] = + overload.parameter_types() + else { + continue; + }; - let union_with_default = |ty| UnionType::from_elements(db, [ty, default]); - - // TODO: we could emit a diagnostic here (if default is not set) - overload.set_return_type( - match instance_ty.static_member(db, attr_name.value(db)) { - Symbol::Type(ty, Boundness::Bound) => { - if instance_ty.is_fully_static(db) { - ty - } else { - // Here, we attempt to model the fact that an attribute lookup on - // a non-fully static type could fail. This is an approximation, - // as there are gradual types like `tuple[Any]`, on which a lookup - // of (e.g. of the `index` method) would always succeed. + let Some(attr_name) = attr_name.into_string_literal() else { + continue; + }; + let default = if let Some(default) = default { + *default + } else { + Type::Never + }; + + let union_with_default = + |ty| UnionType::from_elements(db, [ty, default]); + + // TODO: we could emit a diagnostic here (if default is not set) + overload.set_return_type( + match instance_ty.static_member(db, attr_name.value(db)) { + Symbol::Type(ty, Boundness::Bound) => { + if instance_ty.is_fully_static(db) { + ty + } else { + // Here, we attempt to model the fact that an attribute lookup on + // a non-fully static type could fail. This is an approximation, + // as there are gradual types like `tuple[Any]`, on which a lookup + // of (e.g. of the `index` method) would always succeed. + + union_with_default(ty) + } + } + Symbol::Type(ty, Boundness::PossiblyUnbound) => { union_with_default(ty) } + Symbol::Unbound => default, + }, + ); + } + + Some(KnownFunction::Dataclass) => { + if let [init, repr, eq, order, unsafe_hash, frozen, match_args, kw_only, slots, weakref_slot] = + overload.parameter_types() + { + let mut params = DataclassParams::empty(); + + if to_bool(init, true) { + params |= DataclassParams::INIT; } - Symbol::Type(ty, Boundness::PossiblyUnbound) => { - union_with_default(ty) + if to_bool(repr, true) { + params |= DataclassParams::REPR; + } + if to_bool(eq, true) { + params |= DataclassParams::EQ; + } + if to_bool(order, false) { + params |= DataclassParams::ORDER; + } + if to_bool(unsafe_hash, false) { + params |= DataclassParams::UNSAFE_HASH; + } + if to_bool(frozen, false) { + params |= DataclassParams::FROZEN; + } + if to_bool(match_args, true) { + params |= DataclassParams::MATCH_ARGS; + } + if to_bool(kw_only, false) { + params |= DataclassParams::KW_ONLY; + } + if to_bool(slots, false) { + params |= DataclassParams::SLOTS; + } + if to_bool(weakref_slot, false) { + params |= DataclassParams::WEAKREF_SLOT; } - Symbol::Unbound => default, - }, - ); - } - - Some(KnownFunction::Dataclass) => { - if let [init, repr, eq, order, unsafe_hash, frozen, match_args, kw_only, slots, weakref_slot] = - overload.parameter_types() - { - let mut params = DataclassParams::empty(); - if to_bool(init, true) { - params |= DataclassParams::INIT; - } - if to_bool(repr, true) { - params |= DataclassParams::REPR; + overload.set_return_type(Type::DataclassDecorator(params)); } - if to_bool(eq, true) { - params |= DataclassParams::EQ; - } - if to_bool(order, false) { - params |= DataclassParams::ORDER; - } - if to_bool(unsafe_hash, false) { - params |= DataclassParams::UNSAFE_HASH; - } - if to_bool(frozen, false) { - params |= DataclassParams::FROZEN; - } - if to_bool(match_args, true) { - params |= DataclassParams::MATCH_ARGS; - } - if to_bool(kw_only, false) { - params |= DataclassParams::KW_ONLY; - } - if to_bool(slots, false) { - params |= DataclassParams::SLOTS; - } - if to_bool(weakref_slot, false) { - params |= DataclassParams::WEAKREF_SLOT; - } - - overload.set_return_type(Type::DataclassDecorator(params)); } - } - Some(KnownFunction::DataclassTransform) => { - if let [eq_default, order_default, kw_only_default, frozen_default, _field_specifiers, _kwargs] = - overload.parameter_types() - { - let mut params = DataclassTransformerParams::empty(); + Some(KnownFunction::DataclassTransform) => { + if let [eq_default, order_default, kw_only_default, frozen_default, _field_specifiers, _kwargs] = + overload.parameter_types() + { + let mut params = DataclassTransformerParams::empty(); - if to_bool(eq_default, true) { - params |= DataclassTransformerParams::EQ_DEFAULT; - } - if to_bool(order_default, false) { - params |= DataclassTransformerParams::ORDER_DEFAULT; - } - if to_bool(kw_only_default, false) { - params |= DataclassTransformerParams::KW_ONLY_DEFAULT; - } - if to_bool(frozen_default, false) { - params |= DataclassTransformerParams::FROZEN_DEFAULT; - } + if to_bool(eq_default, true) { + params |= DataclassTransformerParams::EQ_DEFAULT; + } + if to_bool(order_default, false) { + params |= DataclassTransformerParams::ORDER_DEFAULT; + } + if to_bool(kw_only_default, false) { + params |= DataclassTransformerParams::KW_ONLY_DEFAULT; + } + if to_bool(frozen_default, false) { + params |= DataclassTransformerParams::FROZEN_DEFAULT; + } - overload.set_return_type(Type::DataclassTransformer(params)); + overload.set_return_type(Type::DataclassTransformer(params)); + } } - } - _ => { - if let Some(params) = function_type.dataclass_transformer_params(db) { - // This is a call to a custom function that was decorated with `@dataclass_transformer`. - // If this function was called with a keyword argument like `order=False`, we extract - // the argument type and overwrite the corresponding flag in `dataclass_params` after - // constructing them from the `dataclass_transformer`-parameter defaults. - - let mut dataclass_params = DataclassParams::from(params); - - if let Some(Some(Type::BooleanLiteral(order))) = callable_signature - .iter() - .nth(overload_index) - .and_then(|signature| { - let (idx, _) = - signature.parameters().keyword_by_name("order")?; - overload.parameter_types().get(idx) - }) - { - dataclass_params.set(DataclassParams::ORDER, *order); - } + _ => { + if let Some(params) = function_type.dataclass_transformer_params(db) { + // This is a call to a custom function that was decorated with `@dataclass_transformer`. + // If this function was called with a keyword argument like `order=False`, we extract + // the argument type and overwrite the corresponding flag in `dataclass_params` after + // constructing them from the `dataclass_transformer`-parameter defaults. + + let mut dataclass_params = DataclassParams::from(params); + + if let Some(Some(Type::BooleanLiteral(order))) = callable_signature + .iter() + .nth(overload_index) + .and_then(|signature| { + let (idx, _) = + signature.parameters().keyword_by_name("order")?; + overload.parameter_types().get(idx) + }) + { + dataclass_params.set(DataclassParams::ORDER, *order); + } - overload.set_return_type(Type::DataclassDecorator(dataclass_params)); + overload + .set_return_type(Type::DataclassDecorator(dataclass_params)); + } } - } - }, - - Type::ClassLiteral(class) => match class.known(db) { - Some(KnownClass::Bool) => match overload.parameter_types() { - [Some(arg)] => overload.set_return_type(arg.bool(db).into_type(db)), - [None] => overload.set_return_type(Type::BooleanLiteral(false)), - _ => {} }, - Some(KnownClass::Str) if overload_index == 0 => { - match overload.parameter_types() { - [Some(arg)] => overload.set_return_type(arg.str(db)), - [None] => overload.set_return_type(Type::string_literal(db, "")), + Type::ClassLiteral(class) => match class.known(db) { + Some(KnownClass::Bool) => match overload.parameter_types() { + [Some(arg)] => overload.set_return_type(arg.bool(db).into_type(db)), + [None] => overload.set_return_type(Type::BooleanLiteral(false)), _ => {} + }, + + Some(KnownClass::Str) if overload_index == 0 => { + match overload.parameter_types() { + [Some(arg)] => overload.set_return_type(arg.str(db)), + [None] => overload.set_return_type(Type::string_literal(db, "")), + _ => {} + } } - } - Some(KnownClass::Type) if overload_index == 0 => { - if let [Some(arg)] = overload.parameter_types() { - overload.set_return_type(arg.to_meta_type(db)); + Some(KnownClass::Type) if overload_index == 0 => { + if let [Some(arg)] = overload.parameter_types() { + overload.set_return_type(arg.to_meta_type(db)); + } } - } - Some(KnownClass::Property) => { - if let [getter, setter, ..] = overload.parameter_types() { - overload.set_return_type(Type::PropertyInstance( - PropertyInstanceType::new(db, *getter, *setter), - )); + Some(KnownClass::Property) => { + if let [getter, setter, ..] = overload.parameter_types() { + overload.set_return_type(Type::PropertyInstance( + PropertyInstanceType::new(db, *getter, *setter), + )); + } } + + _ => {} + }, + + Type::KnownInstance(KnownInstanceType::TypedDict) => { + overload.set_return_type(todo_type!("TypedDict")); } + // Not a special case _ => {} - }, - - Type::KnownInstance(KnownInstanceType::TypedDict) => { - overload.set_return_type(todo_type!("TypedDict")); } - - // Not a special case - _ => {} } } } @@ -904,25 +940,25 @@ impl<'db> CallableBinding<'db> { /// Returns whether there were any errors binding this call site. If the callable has multiple /// overloads, they must _all_ have errors. pub(crate) fn has_binding_errors(&self) -> bool { - self.matching_overload().is_none() + self.matching_overloads().next().is_none() } - /// Returns the overload that matched for this call binding. Returns `None` if none of the - /// overloads matched. - pub(crate) fn matching_overload(&self) -> Option<(usize, &Binding<'db>)> { + /// Returns an iterator over all the overloads that matched for this call binding. + pub(crate) fn matching_overloads(&self) -> impl Iterator)> { self.overloads .iter() .enumerate() - .find(|(_, overload)| overload.as_result().is_ok()) + .filter(|(_, overload)| overload.as_result().is_ok()) } - /// Returns the overload that matched for this call binding. Returns `None` if none of the - /// overloads matched. - pub(crate) fn matching_overload_mut(&mut self) -> Option<(usize, &mut Binding<'db>)> { + /// Returns an iterator over all the mutable overloads that matched for this call binding. + pub(crate) fn matching_overloads_mut( + &mut self, + ) -> impl Iterator)> { self.overloads .iter_mut() .enumerate() - .find(|(_, overload)| overload.as_result().is_ok()) + .filter(|(_, overload)| overload.as_result().is_ok()) } /// Returns the return type of this call. For a valid call, this is the return type of the @@ -930,9 +966,16 @@ impl<'db> CallableBinding<'db> { /// function, this is the return type of the function. For an invalid call to an overloaded /// function, we return `Type::unknown`, since we cannot make any useful conclusions about /// which overload was intended to be called. - pub(crate) fn return_type(&self) -> Type<'db> { - if let Some((_, overload)) = self.matching_overload() { - return overload.return_type(); + pub(crate) fn return_type(&self, db: &'db dyn Db) -> Type<'db> { + let mut overloads = self.matching_overloads(); + if let Some(first) = overloads.next() { + return std::iter::once(first) + .chain(overloads) + .map(|(_, overload)| overload.return_type()) + .fold(IntersectionBuilder::new(db), |builder, ty| { + builder.add_positive(ty) + }) + .build(); } if let [overload] = self.overloads.as_slice() { return overload.return_type(); @@ -1250,6 +1293,20 @@ impl<'db> Binding<'db> { &self.parameter_tys } + pub(crate) fn arguments_for_parameter<'a>( + &'a self, + argument_types: &'a CallArgumentTypes<'a, 'db>, + parameter_index: usize, + ) -> impl Iterator, Type<'db>)> + 'a { + argument_types + .iter() + .zip(&self.argument_parameters) + .filter(move |(_, argument_parameter)| { + argument_parameter.is_some_and(|ap| ap == parameter_index) + }) + .map(|(arg_and_type, _)| arg_and_type) + } + fn report_diagnostics( &self, context: &InferContext<'db>, diff --git a/crates/red_knot_python_semantic/src/types/diagnostic.rs b/crates/red_knot_python_semantic/src/types/diagnostic.rs index e051de715bd5bc..63d5a18e4baef0 100644 --- a/crates/red_knot_python_semantic/src/types/diagnostic.rs +++ b/crates/red_knot_python_semantic/src/types/diagnostic.rs @@ -35,6 +35,7 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) { registry.register_lint(&INVALID_CONTEXT_MANAGER); registry.register_lint(&INVALID_DECLARATION); registry.register_lint(&INVALID_EXCEPTION_CAUGHT); + registry.register_lint(&INVALID_LEGACY_TYPE_VARIABLE); registry.register_lint(&INVALID_METACLASS); registry.register_lint(&INVALID_PARAMETER_DEFAULT); registry.register_lint(&INVALID_PROTOCOL); @@ -391,6 +392,34 @@ declare_lint! { } } +declare_lint! { + /// ## What it does + /// Checks for the creation of invalid legacy `TypeVar`s + /// + /// ## Why is this bad? + /// There are several requirements that you must follow when creating a legacy `TypeVar`. + /// + /// ## Examples + /// ```python + /// from typing import TypeVar + /// + /// T = TypeVar("T") # okay + /// Q = TypeVar("S") # error: TypeVar name must match the variable it's assigned to + /// T = TypeVar("T") # error: TypeVars should not be redefined + /// + /// # error: TypeVar must be immediately assigned to a variable + /// def f(t: TypeVar("U")): ... + /// ``` + /// + /// ## References + /// - [Typing spec: Generics](https://typing.python.org/en/latest/spec/generics.html#introduction) + pub(crate) static INVALID_LEGACY_TYPE_VARIABLE = { + summary: "detects invalid legacy type variables", + status: LintStatus::preview("1.0.0"), + default_level: Level::Error, + } +} + declare_lint! { /// ## What it does /// Checks for arguments to `metaclass=` that are invalid. @@ -1314,7 +1343,7 @@ pub(crate) fn report_invalid_arguments_to_annotated( builder.into_diagnostic(format_args!( "Special form `{}` expected at least 2 arguments \ (one type and at least one metadata element)", - KnownInstanceType::Annotated.repr(context.db()) + KnownInstanceType::Annotated.repr() )); } @@ -1362,7 +1391,7 @@ pub(crate) fn report_invalid_arguments_to_callable( }; builder.into_diagnostic(format_args!( "Special form `{}` expected exactly two arguments (parameter types and return type)", - KnownInstanceType::Callable.repr(context.db()) + KnownInstanceType::Callable.repr() )); } diff --git a/crates/red_knot_python_semantic/src/types/display.rs b/crates/red_knot_python_semantic/src/types/display.rs index 4220b0cf41f138..2633938ae9285f 100644 --- a/crates/red_knot_python_semantic/src/types/display.rs +++ b/crates/red_knot_python_semantic/src/types/display.rs @@ -94,7 +94,7 @@ impl Display for DisplayRepresentation<'_> { SubclassOfInner::Class(class) => write!(f, "type[{}]", class.name(self.db)), SubclassOfInner::Dynamic(dynamic) => write!(f, "type[{dynamic}]"), }, - Type::KnownInstance(known_instance) => f.write_str(known_instance.repr(self.db)), + Type::KnownInstance(known_instance) => f.write_str(known_instance.repr()), Type::FunctionLiteral(function) => { let signature = function.signature(self.db); diff --git a/crates/red_knot_python_semantic/src/types/generics.rs b/crates/red_knot_python_semantic/src/types/generics.rs index b1c0355ebf240b..af660b86ae7eed 100644 --- a/crates/red_knot_python_semantic/src/types/generics.rs +++ b/crates/red_knot_python_semantic/src/types/generics.rs @@ -5,9 +5,9 @@ use crate::semantic_index::SemanticIndex; use crate::types::signatures::{Parameter, Parameters, Signature}; use crate::types::{ declaration_type, KnownInstanceType, Type, TypeVarBoundOrConstraints, TypeVarInstance, - UnionBuilder, UnionType, + UnionType, }; -use crate::Db; +use crate::{Db, FxOrderSet}; /// A list of formal type variables for a generic function, class, or type alias. /// @@ -20,6 +20,7 @@ pub struct GenericContext<'db> { } impl<'db> GenericContext<'db> { + /// Creates a generic context from a list of PEP-695 type parameters. pub(crate) fn from_type_params( db: &'db dyn Db, index: &'db SemanticIndex<'db>, @@ -53,6 +54,32 @@ impl<'db> GenericContext<'db> { } } + /// Creates a generic context from the legecy `TypeVar`s that appear in a function parameter + /// list. + pub(crate) fn from_function_params( + db: &'db dyn Db, + parameters: &Parameters<'db>, + return_type: Option>, + ) -> Option { + let mut variables = FxOrderSet::default(); + for param in parameters { + if let Some(ty) = param.annotated_type() { + ty.find_legacy_typevars(db, &mut variables); + } + if let Some(ty) = param.default_type() { + ty.find_legacy_typevars(db, &mut variables); + } + } + if let Some(ty) = return_type { + ty.find_legacy_typevars(db, &mut variables); + } + if variables.is_empty() { + return None; + } + let variables: Box<[_]> = variables.into_iter().collect(); + Some(Self::new(db, variables)) + } + pub(crate) fn signature(self, db: &'db dyn Db) -> Signature<'db> { let parameters = Parameters::new( self.variables(db) @@ -303,7 +330,7 @@ impl<'db> Specialization<'db> { /// specialization of a generic function. pub(crate) struct SpecializationBuilder<'db> { db: &'db dyn Db, - types: FxHashMap, UnionBuilder<'db>>, + types: FxHashMap, Type<'db>>, } impl<'db> SpecializationBuilder<'db> { @@ -320,8 +347,8 @@ impl<'db> SpecializationBuilder<'db> { .iter() .map(|variable| { self.types - .remove(variable) - .map(UnionBuilder::build) + .get(variable) + .copied() .unwrap_or(variable.default_ty(self.db).unwrap_or(Type::unknown())) }) .collect(); @@ -329,17 +356,21 @@ impl<'db> SpecializationBuilder<'db> { } fn add_type_mapping(&mut self, typevar: TypeVarInstance<'db>, ty: Type<'db>) { - let builder = self - .types + self.types .entry(typevar) - .or_insert_with(|| UnionBuilder::new(self.db)); - builder.add_in_place(ty); + .and_modify(|existing| { + *existing = UnionType::from_elements(self.db, [*existing, ty]); + }) + .or_insert(ty); } pub(crate) fn infer(&mut self, formal: Type<'db>, actual: Type<'db>) { - // If the actual type is already assignable to the formal type, then return without adding - // any new type mappings. (Note that if the formal type contains any typevars, this check - // will fail, since no non-typevar types are assignable to a typevar.) + // If the actual type is a subtype of the formal type, then return without adding any new + // type mappings. (Note that if the formal type contains any typevars, this check will + // fail, since no non-typevar types are assignable to a typevar. Also note that we are + // checking _subtyping_, not _assignability_, so that we do specialize typevars to dynamic + // argument types; and we have a special case for `Never`, which is a subtype of all types, + // but which we also do want as a specialization candidate.) // // In particular, this handles a case like // @@ -350,7 +381,7 @@ impl<'db> SpecializationBuilder<'db> { // ``` // // without specializing `T` to `None`. - if actual.is_assignable_to(self.db, formal) { + if !actual.is_never() && actual.is_subtype_of(self.db, formal) { return; } diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index e9448d83b09698..edcc1acc06b4fd 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -73,9 +73,9 @@ use crate::types::diagnostic::{ CALL_POSSIBLY_UNBOUND_METHOD, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS, CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO, DUPLICATE_BASE, INCONSISTENT_MRO, INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, INVALID_DECLARATION, - INVALID_PARAMETER_DEFAULT, INVALID_TYPE_FORM, INVALID_TYPE_VARIABLE_CONSTRAINTS, - POSSIBLY_UNBOUND_IMPORT, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_IMPORT, - UNSUPPORTED_OPERATOR, + INVALID_LEGACY_TYPE_VARIABLE, INVALID_PARAMETER_DEFAULT, INVALID_TYPE_FORM, + INVALID_TYPE_VARIABLE_CONSTRAINTS, POSSIBLY_UNBOUND_IMPORT, UNDEFINED_REVEAL, + UNRESOLVED_ATTRIBUTE, UNRESOLVED_IMPORT, UNSUPPORTED_OPERATOR, }; use crate::types::generics::GenericContext; use crate::types::mro::MroErrorKind; @@ -87,7 +87,8 @@ use crate::types::{ MemberLookupPolicy, MetaclassCandidate, Parameter, ParameterForm, Parameters, Signature, Signatures, SliceLiteralType, StringLiteralType, SubclassOfType, Symbol, SymbolAndQualifiers, Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers, TypeArrayDisplay, - TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder, UnionType, + TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, UnionBuilder, + UnionType, }; use crate::unpack::{Unpack, UnpackPosition}; use crate::util::subscript::{PyIndex, PySlice}; @@ -2204,6 +2205,7 @@ impl<'db> TypeInferenceBuilder<'db> { definition, bound_or_constraint, default_ty, + TypeVarKind::Pep695, ))); self.add_declaration_with_binding( node.into(), @@ -3733,7 +3735,9 @@ impl<'db> TypeInferenceBuilder<'db> { ast::Expr::Named(named) => self.infer_named_expression(named), ast::Expr::If(if_expression) => self.infer_if_expression(if_expression), ast::Expr::Lambda(lambda_expression) => self.infer_lambda_expression(lambda_expression), - ast::Expr::Call(call_expression) => self.infer_call_expression(call_expression), + ast::Expr::Call(call_expression) => { + self.infer_call_expression(expression, call_expression) + } ast::Expr::Starred(starred) => self.infer_starred_expression(starred), ast::Expr::Yield(yield_expression) => self.infer_yield_expression(yield_expression), ast::Expr::YieldFrom(yield_from) => self.infer_yield_from_expression(yield_from), @@ -4277,7 +4281,11 @@ impl<'db> TypeInferenceBuilder<'db> { }) } - fn infer_call_expression(&mut self, call_expression: &ast::ExprCall) -> Type<'db> { + fn infer_call_expression( + &mut self, + call_expression_node: &ast::Expr, + call_expression: &ast::ExprCall, + ) -> Type<'db> { let ast::ExprCall { range: _, func, @@ -4332,6 +4340,7 @@ impl<'db> TypeInferenceBuilder<'db> { | KnownClass::Object | KnownClass::Property | KnownClass::Super + | KnownClass::TypeVar ) ) { @@ -4356,261 +4365,376 @@ impl<'db> TypeInferenceBuilder<'db> { Ok(mut bindings) => { for binding in &mut bindings { let binding_type = binding.callable_type; - let Some((_, overload)) = binding.matching_overload_mut() else { - continue; - }; - - match binding_type { - Type::FunctionLiteral(function_literal) => { - let Some(known_function) = function_literal.known(self.db()) else { - continue; - }; + for (_, overload) in binding.matching_overloads_mut() { + match binding_type { + Type::FunctionLiteral(function_literal) => { + let Some(known_function) = function_literal.known(self.db()) else { + continue; + }; - match known_function { - KnownFunction::RevealType => { - if let [Some(revealed_type)] = overload.parameter_types() { - if let Some(builder) = self.context.report_diagnostic( - DiagnosticId::RevealedType, - Severity::Info, - ) { - let mut diag = builder.into_diagnostic("Revealed type"); - let span = self.context.span(call_expression); - diag.annotate(Annotation::primary(span).message( - format_args!( - "`{}`", - revealed_type.display(self.db()) - ), - )); + match known_function { + KnownFunction::RevealType => { + if let [Some(revealed_type)] = overload.parameter_types() { + if let Some(builder) = self.context.report_diagnostic( + DiagnosticId::RevealedType, + Severity::Info, + ) { + let mut diag = + builder.into_diagnostic("Revealed type"); + let span = self.context.span(call_expression); + diag.annotate(Annotation::primary(span).message( + format_args!( + "`{}`", + revealed_type.display(self.db()) + ), + )); + } } } - } - KnownFunction::AssertType => { - if let [Some(actual_ty), Some(asserted_ty)] = - overload.parameter_types() - { - if !actual_ty - .is_gradual_equivalent_to(self.db(), *asserted_ty) + KnownFunction::AssertType => { + if let [Some(actual_ty), Some(asserted_ty)] = + overload.parameter_types() { - if let Some(builder) = self.context.report_lint( - &TYPE_ASSERTION_FAILURE, - call_expression, - ) { - builder.into_diagnostic(format_args!( - "Actual type `{}` is not the same \ + if !actual_ty + .is_gradual_equivalent_to(self.db(), *asserted_ty) + { + if let Some(builder) = self.context.report_lint( + &TYPE_ASSERTION_FAILURE, + call_expression, + ) { + builder.into_diagnostic(format_args!( + "Actual type `{}` is not the same \ as asserted type `{}`", - actual_ty.display(self.db()), - asserted_ty.display(self.db()), - )); + actual_ty.display(self.db()), + asserted_ty.display(self.db()), + )); + } } } } - } - KnownFunction::AssertNever => { - if let [Some(actual_ty)] = overload.parameter_types() { - if !actual_ty.is_equivalent_to(self.db(), Type::Never) { - if let Some(builder) = self.context.report_lint( - &TYPE_ASSERTION_FAILURE, - call_expression, - ) { - builder.into_diagnostic(format_args!( - "Expected type `Never`, got `{}` instead", - actual_ty.display(self.db()), - )); + KnownFunction::AssertNever => { + if let [Some(actual_ty)] = overload.parameter_types() { + if !actual_ty.is_equivalent_to(self.db(), Type::Never) { + if let Some(builder) = self.context.report_lint( + &TYPE_ASSERTION_FAILURE, + call_expression, + ) { + builder.into_diagnostic(format_args!( + "Expected type `Never`, got `{}` instead", + actual_ty.display(self.db()), + )); + } } } } - } - KnownFunction::StaticAssert => { - if let [Some(parameter_ty), message] = - overload.parameter_types() - { - let truthiness = match parameter_ty.try_bool(self.db()) { - Ok(truthiness) => truthiness, - Err(err) => { - let condition = arguments - .find_argument("condition", 0) - .map(|argument| match argument { + KnownFunction::StaticAssert => { + if let [Some(parameter_ty), message] = + overload.parameter_types() + { + let truthiness = match parameter_ty.try_bool(self.db()) + { + Ok(truthiness) => truthiness, + Err(err) => { + let condition = arguments + .find_argument("condition", 0) + .map(|argument| { + match argument { ruff_python_ast::ArgOrKeyword::Arg( expr, ) => ast::AnyNodeRef::from(expr), ruff_python_ast::ArgOrKeyword::Keyword( keyword, ) => ast::AnyNodeRef::from(keyword), - }) - .unwrap_or(ast::AnyNodeRef::from( - call_expression, - )); + } + }) + .unwrap_or(ast::AnyNodeRef::from( + call_expression, + )); - err.report_diagnostic(&self.context, condition); + err.report_diagnostic(&self.context, condition); - continue; - } - }; + continue; + } + }; - if let Some(builder) = self - .context - .report_lint(&STATIC_ASSERT_ERROR, call_expression) - { - if !truthiness.is_always_true() { - if let Some(message) = message - .and_then(Type::into_string_literal) - .map(|s| &**s.value(self.db())) - { - builder.into_diagnostic(format_args!( - "Static assertion error: {message}" - )); - } else if *parameter_ty - == Type::BooleanLiteral(false) - { - builder.into_diagnostic( - "Static assertion error: \ + if let Some(builder) = self + .context + .report_lint(&STATIC_ASSERT_ERROR, call_expression) + { + if !truthiness.is_always_true() { + if let Some(message) = message + .and_then(Type::into_string_literal) + .map(|s| &**s.value(self.db())) + { + builder.into_diagnostic(format_args!( + "Static assertion error: {message}" + )); + } else if *parameter_ty + == Type::BooleanLiteral(false) + { + builder.into_diagnostic( + "Static assertion error: \ argument evaluates to `False`", - ); - } else if truthiness.is_always_false() { - builder.into_diagnostic(format_args!( - "Static assertion error: \ + ); + } else if truthiness.is_always_false() { + builder.into_diagnostic(format_args!( + "Static assertion error: \ argument of type `{parameter_ty}` \ is statically known to be falsy", - parameter_ty = - parameter_ty.display(self.db()) - )); - } else { - builder.into_diagnostic(format_args!( - "Static assertion error: \ + parameter_ty = + parameter_ty.display(self.db()) + )); + } else { + builder.into_diagnostic(format_args!( + "Static assertion error: \ argument of type `{parameter_ty}` \ has an ambiguous static truthiness", - parameter_ty = - parameter_ty.display(self.db()) - )); + parameter_ty = + parameter_ty.display(self.db()) + )); + } } } } } - } - KnownFunction::Cast => { - if let [Some(casted_type), Some(source_type)] = - overload.parameter_types() - { - let db = self.db(); - if (source_type.is_equivalent_to(db, *casted_type) - || source_type.normalized(db) - == casted_type.normalized(db)) - && !source_type.contains_todo(db) + KnownFunction::Cast => { + if let [Some(casted_type), Some(source_type)] = + overload.parameter_types() { - if let Some(builder) = self - .context - .report_lint(&REDUNDANT_CAST, call_expression) + let db = self.db(); + if (source_type.is_equivalent_to(db, *casted_type) + || source_type.normalized(db) + == casted_type.normalized(db)) + && !source_type.contains_todo(db) { - builder.into_diagnostic(format_args!( - "Value is already of type `{}`", - casted_type.display(db), - )); + if let Some(builder) = self + .context + .report_lint(&REDUNDANT_CAST, call_expression) + { + builder.into_diagnostic(format_args!( + "Value is already of type `{}`", + casted_type.display(db), + )); + } } } } - } - KnownFunction::GetProtocolMembers => { - if let [Some(Type::ClassLiteral(class))] = - overload.parameter_types() - { - if !class.is_protocol(self.db()) { - report_bad_argument_to_get_protocol_members( - &self.context, - call_expression, - *class, - ); + KnownFunction::GetProtocolMembers => { + if let [Some(Type::ClassLiteral(class))] = + overload.parameter_types() + { + if !class.is_protocol(self.db()) { + report_bad_argument_to_get_protocol_members( + &self.context, + call_expression, + *class, + ); + } } } - } - KnownFunction::IsInstance | KnownFunction::IsSubclass => { - if let [_, Some(Type::ClassLiteral(class))] = - overload.parameter_types() - { - if let Some(protocol_class) = - class.into_protocol_class(self.db()) + KnownFunction::IsInstance | KnownFunction::IsSubclass => { + if let [_, Some(Type::ClassLiteral(class))] = + overload.parameter_types() { - if !protocol_class.is_runtime_checkable(self.db()) { - report_runtime_check_against_non_runtime_checkable_protocol( + if let Some(protocol_class) = + class.into_protocol_class(self.db()) + { + if !protocol_class.is_runtime_checkable(self.db()) { + report_runtime_check_against_non_runtime_checkable_protocol( &self.context, call_expression, protocol_class, known_function ); + } } } } + _ => {} } - _ => {} } - } - Type::ClassLiteral(class) - if class.is_known(self.db(), KnownClass::Super) => - { - // Handle the case where `super()` is called with no arguments. - // In this case, we need to infer the two arguments: - // 1. The nearest enclosing class - // 2. The first parameter of the current function (typically `self` or `cls`) - match overload.parameter_types() { - [] => { - let scope = self.scope(); - - let Some(enclosing_class) = self.enclosing_class_symbol(scope) - else { - overload.set_return_type(Type::unknown()); - BoundSuperError::UnavailableImplicitArguments - .report_diagnostic( - &self.context, - call_expression.into(), - ); - continue; - }; - - let Some(first_param) = self.first_param_type_in_scope(scope) - else { - overload.set_return_type(Type::unknown()); - BoundSuperError::UnavailableImplicitArguments - .report_diagnostic( - &self.context, - call_expression.into(), - ); - continue; - }; - - let bound_super = BoundSuperType::build( - self.db(), - enclosing_class, - first_param, - ) - .unwrap_or_else(|err| { - err.report_diagnostic( - &self.context, - call_expression.into(), - ); - Type::unknown() - }); - - overload.set_return_type(bound_super); - } - [Some(pivot_class_type), Some(owner_type)] => { - let bound_super = BoundSuperType::build( - self.db(), - *pivot_class_type, - *owner_type, - ) - .unwrap_or_else(|err| { - err.report_diagnostic( - &self.context, - call_expression.into(), - ); - Type::unknown() - }); - - overload.set_return_type(bound_super); + + Type::ClassLiteral(class) => { + let Some(known_class) = class.known(self.db()) else { + continue; + }; + + match known_class { + KnownClass::Super => { + // Handle the case where `super()` is called with no arguments. + // In this case, we need to infer the two arguments: + // 1. The nearest enclosing class + // 2. The first parameter of the current function (typically `self` or `cls`) + match overload.parameter_types() { + [] => { + let scope = self.scope(); + + let Some(enclosing_class) = + self.enclosing_class_symbol(scope) + else { + overload.set_return_type(Type::unknown()); + BoundSuperError::UnavailableImplicitArguments + .report_diagnostic( + &self.context, + call_expression.into(), + ); + continue; + }; + + let Some(first_param) = + self.first_param_type_in_scope(scope) + else { + overload.set_return_type(Type::unknown()); + BoundSuperError::UnavailableImplicitArguments + .report_diagnostic( + &self.context, + call_expression.into(), + ); + continue; + }; + + let bound_super = BoundSuperType::build( + self.db(), + enclosing_class, + first_param, + ) + .unwrap_or_else(|err| { + err.report_diagnostic( + &self.context, + call_expression.into(), + ); + Type::unknown() + }); + + overload.set_return_type(bound_super); + } + [Some(pivot_class_type), Some(owner_type)] => { + let bound_super = BoundSuperType::build( + self.db(), + *pivot_class_type, + *owner_type, + ) + .unwrap_or_else(|err| { + err.report_diagnostic( + &self.context, + call_expression.into(), + ); + Type::unknown() + }); + + overload.set_return_type(bound_super); + } + _ => (), + } + } + + KnownClass::TypeVar => { + let assigned_to = (self.index) + .try_expression(call_expression_node) + .and_then(|expr| expr.assigned_to(self.db())); + + let Some(target) = + assigned_to.as_ref().and_then(|assigned_to| { + match assigned_to.node().targets.as_slice() { + [ast::Expr::Name(target)] => Some(target), + _ => None, + } + }) + else { + if let Some(builder) = self.context.report_lint( + &INVALID_LEGACY_TYPE_VARIABLE, + call_expression, + ) { + builder.into_diagnostic(format_args!( + "A legacy `typing.TypeVar` must be immediately assigned to a variable", + )); + } + continue; + }; + + let [Some(name_param), constraints, bound, default, _contravariant, _covariant, _infer_variance] = + overload.parameter_types() + else { + continue; + }; + + let name_param = name_param + .into_string_literal() + .map(|name| name.value(self.db()).as_ref()); + if name_param + .is_none_or(|name_param| name_param != target.id) + { + if let Some(builder) = self.context.report_lint( + &INVALID_LEGACY_TYPE_VARIABLE, + call_expression, + ) { + builder.into_diagnostic(format_args!( + "The name of a legacy `typing.TypeVar`{} must match \ + the name of the variable it is assigned to (`{}`)", + if let Some(name_param) = name_param { + format!(" (`{name_param}`)") + } else { + String::new() + }, + target.id, + )); + } + continue; + } + + let bound_or_constraint = match (bound, constraints) { + (Some(bound), None) => { + Some(TypeVarBoundOrConstraints::UpperBound(*bound)) + } + + (None, Some(_constraints)) => { + // We don't use UnionType::from_elements or UnionBuilder here, + // because we don't want to simplify the list of constraints like + // we do with the elements of an actual union type. + // TODO: Consider using a new `OneOfType` connective here instead, + // since that more accurately represents the actual semantics of + // typevar constraints. + let elements = UnionType::new( + self.db(), + overload + .arguments_for_parameter( + &call_argument_types, + 1, + ) + .map(|(_, ty)| ty) + .collect::>(), + ); + Some(TypeVarBoundOrConstraints::Constraints( + elements, + )) + } + + // TODO: Emit a diagnostic that TypeVar cannot be both bounded and + // constrained + (Some(_), Some(_)) => continue, + + (None, None) => None, + }; + + let containing_assignment = + self.index.expect_single_definition(target); + overload.set_return_type(Type::KnownInstance( + KnownInstanceType::TypeVar(TypeVarInstance::new( + self.db(), + target.id.clone(), + containing_assignment, + bound_or_constraint, + *default, + TypeVarKind::Legacy, + )), + )); + } + + _ => (), } - _ => (), } + _ => (), } - _ => (), } } bindings.return_type(self.db()) @@ -6254,7 +6378,8 @@ impl<'db> TypeInferenceBuilder<'db> { .next() .expect("valid bindings should have one callable"); let (_, overload) = callable - .matching_overload() + .matching_overloads() + .next() .expect("valid bindings should have matching overload"); let specialization = generic_context.specialize( self.db(), @@ -6509,7 +6634,12 @@ impl<'db> TypeInferenceBuilder<'db> { if class.generic_context(self.db()).is_some() { // TODO: specialize the generic class using these explicit type - // variable assignments + // variable assignments. This branch is only encountered when an + // explicit class specialization appears inside of some other subscript + // expression, e.g. `tuple[list[int], ...]`. We have already inferred + // the type of the outer subscript slice as a value expression, which + // means we can't re-infer the inner specialization here as a type + // expression. return value_ty; } } @@ -6753,7 +6883,7 @@ impl<'db> TypeInferenceBuilder<'db> { builder.into_diagnostic(format_args!( "Type qualifier `{type_qualifier}` \ expects exactly one type parameter", - type_qualifier = known_instance.repr(self.db()), + type_qualifier = known_instance.repr(), )); } Type::unknown().into() @@ -7111,7 +7241,7 @@ impl<'db> TypeInferenceBuilder<'db> { } ast::Expr::Call(call_expr) => { - self.infer_call_expression(call_expr); + self.infer_call_expression(expression, call_expr); self.report_invalid_type_expression( expression, format_args!("Function calls are not allowed in type expressions"), @@ -7531,7 +7661,7 @@ impl<'db> TypeInferenceBuilder<'db> { if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!( "Special form `{}` expected exactly one type parameter", - known_instance.repr(db) + known_instance.repr() )); } Type::unknown() @@ -7558,7 +7688,7 @@ impl<'db> TypeInferenceBuilder<'db> { if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!( "Special form `{}` expected exactly one type parameter", - known_instance.repr(db) + known_instance.repr() )); } Type::unknown() @@ -7574,7 +7704,7 @@ impl<'db> TypeInferenceBuilder<'db> { if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!( "Special form `{}` expected exactly one type parameter", - known_instance.repr(db) + known_instance.repr() )); } Type::unknown() @@ -7607,7 +7737,7 @@ impl<'db> TypeInferenceBuilder<'db> { "Expected the first argument to `{}` \ to be a callable object, \ but got an object of type `{}`", - known_instance.repr(db), + known_instance.repr(), argument_type.display(db) )); } @@ -7672,7 +7802,7 @@ impl<'db> TypeInferenceBuilder<'db> { builder.into_diagnostic(format_args!( "Type qualifier `{}` is not allowed in type expressions \ (only in annotation expressions)", - known_instance.repr(db) + known_instance.repr() )); } self.infer_type_expression(arguments_slice) @@ -7715,7 +7845,7 @@ impl<'db> TypeInferenceBuilder<'db> { if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!( "Type `{}` expected no type parameter", - known_instance.repr(db) + known_instance.repr() )); } Type::unknown() @@ -7729,7 +7859,7 @@ impl<'db> TypeInferenceBuilder<'db> { if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!( "Special form `{}` expected no type parameter", - known_instance.repr(db) + known_instance.repr() )); } Type::unknown() @@ -7740,7 +7870,7 @@ impl<'db> TypeInferenceBuilder<'db> { if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { let mut diag = builder.into_diagnostic(format_args!( "Type `{}` expected no type parameter", - known_instance.repr(db) + known_instance.repr() )); diag.info("Did you mean to use `Literal[...]` instead?"); } @@ -8288,7 +8418,7 @@ mod tests { constraints: Option<&[&'static str]>, default: Option<&'static str>| { let var_ty = get_symbol(&db, "src/a.py", &["f"], var).expect_type(); - assert_eq!(var_ty.display(&db).to_string(), var); + assert_eq!(var_ty.display(&db).to_string(), "typing.TypeVar"); let expected_name_ty = format!(r#"Literal["{var}"]"#); let name_ty = var_ty.member(&db, "__name__").symbol.expect_type(); diff --git a/crates/red_knot_python_semantic/src/types/known_instance.rs b/crates/red_knot_python_semantic/src/types/known_instance.rs index 01316c1c5698bb..d6da1d76d42998 100644 --- a/crates/red_knot_python_semantic/src/types/known_instance.rs +++ b/crates/red_knot_python_semantic/src/types/known_instance.rs @@ -109,6 +109,10 @@ impl<'db> KnownInstanceType<'db> { | Self::Literal | Self::LiteralString | Self::Optional + // This is a legacy `TypeVar` _outside_ of any generic class or function, so it's + // AlwaysTrue. The truthiness of a typevar inside of a generic class or function + // depends on its bounds and constraints; but that's represented by `Type::TypeVar` and + // handled in elsewhere. | Self::TypeVar(_) | Self::Union | Self::NoReturn @@ -152,7 +156,7 @@ impl<'db> KnownInstanceType<'db> { } /// Return the repr of the symbol at runtime - pub(crate) fn repr(self, db: &'db dyn Db) -> &'db str { + pub(crate) fn repr(self) -> &'db str { match self { Self::Annotated => "typing.Annotated", Self::Literal => "typing.Literal", @@ -188,7 +192,10 @@ impl<'db> KnownInstanceType<'db> { Self::Protocol => "typing.Protocol", Self::Generic => "typing.Generic", Self::ReadOnly => "typing.ReadOnly", - Self::TypeVar(typevar) => typevar.name(db), + // This is a legacy `TypeVar` _outside_ of any generic class or function, so we render + // it as an instance of `typing.TypeVar`. Inside of a generic class or function, we'll + // have a `Type::TypeVar(_)`, which is rendered as the typevar's name. + Self::TypeVar(_) => "typing.TypeVar", Self::TypeAliasType(_) => "typing.TypeAliasType", Self::Unknown => "knot_extensions.Unknown", Self::AlwaysTruthy => "knot_extensions.AlwaysTruthy", diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index 811965cde91041..a44720d38c3945 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -18,8 +18,8 @@ use smallvec::{smallvec, SmallVec}; use super::{definition_expression_type, DynamicType, Type}; use crate::semantic_index::definition::Definition; use crate::types::generics::{GenericContext, Specialization}; -use crate::types::todo_type; -use crate::Db; +use crate::types::{todo_type, TypeVarInstance}; +use crate::{Db, FxOrderSet}; use ruff_python_ast::{self as ast, name::Name}; /// The signature of a possible union of callables. @@ -267,6 +267,8 @@ impl<'db> Signature<'db> { definition: Definition<'db>, function_node: &ast::StmtFunctionDef, ) -> Self { + let parameters = + Parameters::from_parameters(db, definition, function_node.parameters.as_ref()); let return_ty = function_node.returns.as_ref().map(|returns| { if function_node.is_async { todo_type!("generic types.CoroutineType") @@ -274,15 +276,17 @@ impl<'db> Signature<'db> { definition_expression_type(db, definition, returns.as_ref()) } }); + let legacy_generic_context = + GenericContext::from_function_params(db, ¶meters, return_ty); + + if generic_context.is_some() && legacy_generic_context.is_some() { + // TODO: Raise a diagnostic! + } Self { - generic_context, + generic_context: generic_context.or(legacy_generic_context), inherited_generic_context, - parameters: Parameters::from_parameters( - db, - definition, - function_node.parameters.as_ref(), - ), + parameters, return_ty, } } @@ -315,6 +319,24 @@ impl<'db> Signature<'db> { } } + pub(crate) fn find_legacy_typevars( + &self, + db: &'db dyn Db, + typevars: &mut FxOrderSet>, + ) { + for param in &self.parameters { + if let Some(ty) = param.annotated_type() { + ty.find_legacy_typevars(db, typevars); + } + if let Some(ty) = param.default_type() { + ty.find_legacy_typevars(db, typevars); + } + } + if let Some(ty) = self.return_ty { + ty.find_legacy_typevars(db, typevars); + } + } + /// Return the parameters in this signature. pub(crate) fn parameters(&self) -> &Parameters<'db> { &self.parameters diff --git a/crates/ruff_benchmark/benches/red_knot.rs b/crates/ruff_benchmark/benches/red_knot.rs index e0b0da246ba744..6dab343b05e1dc 100644 --- a/crates/ruff_benchmark/benches/red_knot.rs +++ b/crates/ruff_benchmark/benches/red_knot.rs @@ -59,13 +59,22 @@ type KeyDiagnosticFields = ( Severity, ); -static EXPECTED_TOMLLIB_DIAGNOSTICS: &[KeyDiagnosticFields] = &[( - DiagnosticId::lint("unused-ignore-comment"), - Some("/src/tomllib/_parser.py"), - Some(22299..22333), - "Unused blanket `type: ignore` directive", - Severity::Warning, -)]; +static EXPECTED_TOMLLIB_DIAGNOSTICS: &[KeyDiagnosticFields] = &[ + ( + DiagnosticId::lint("no-matching-overload"), + Some("/src/tomllib/_parser.py"), + Some(2329..2358), + "No overload of bound method `__init__` matches arguments", + Severity::Error, + ), + ( + DiagnosticId::lint("unused-ignore-comment"), + Some("/src/tomllib/_parser.py"), + Some(22299..22333), + "Unused blanket `type: ignore` directive", + Severity::Warning, + ), +]; fn tomllib_path(file: &TestFile) -> SystemPathBuf { SystemPathBuf::from("src").join(file.name()) diff --git a/knot.schema.json b/knot.schema.json index 7349373c0e467d..cb6d425fc72355 100644 --- a/knot.schema.json +++ b/knot.schema.json @@ -446,6 +446,16 @@ } ] }, + "invalid-legacy-type-variable": { + "title": "detects invalid legacy type variables", + "description": "## What it does\nChecks for the creation of invalid legacy `TypeVar`s\n\n## Why is this bad?\nThere are several requirements that you must follow when creating a legacy `TypeVar`.\n\n## Examples\n```python\nfrom typing import TypeVar\n\nT = TypeVar(\"T\") # okay\nQ = TypeVar(\"S\") # error: TypeVar name must match the variable it's assigned to\nT = TypeVar(\"T\") # error: TypeVars should not be redefined\n\n# error: TypeVar must be immediately assigned to a variable\ndef f(t: TypeVar(\"U\")): ...\n```\n\n## References\n- [Typing spec: Generics](https://typing.python.org/en/latest/spec/generics.html#introduction)", + "default": "error", + "oneOf": [ + { + "$ref": "#/definitions/Level" + } + ] + }, "invalid-metaclass": { "title": "detects invalid `metaclass=` arguments", "description": "## What it does\nChecks for arguments to `metaclass=` that are invalid.\n\n## Why is this bad?\nPython allows arbitrary expressions to be used as the argument to `metaclass=`.\nThese expressions, however, need to be callable and accept the same arguments\nas `type.__new__`.\n\n## Example\n\n```python\ndef f(): ...\n\n# TypeError: f() takes 0 positional arguments but 3 were given\nclass B(metaclass=f): ...\n```\n\n## References\n- [Python documentation: Metaclasses](https://docs.python.org/3/reference/datamodel.html#metaclasses)",