Compare commits
1 Commits
codex/stab
...
dhruv/type
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ea31a8923 |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
```
|
||||
|
||||
@@ -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: ...
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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: ...
|
||||
| ^
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
```
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -44,6 +44,17 @@ pub(crate) struct Expression<'db> {
|
||||
#[return_ref]
|
||||
pub(crate) node_ref: AstNodeRef<ast::Expr>,
|
||||
|
||||
/// 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<AstNodeRef<ast::StmtAssign>>,
|
||||
|
||||
/// Should this expression be inferred as a normal expression or a type expression?
|
||||
pub(crate) kind: ExpressionKind,
|
||||
|
||||
|
||||
@@ -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<TypeVarInstance<'db>>,
|
||||
) {
|
||||
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<Specialization<'db>>,
|
||||
s2: Option<Specialization<'db>>,
|
||||
) -> Option<Specialization<'db>> {
|
||||
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<Specialization<'db>> {
|
||||
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<TypeVarInstance<'db>>,
|
||||
) {
|
||||
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<Type<'db>>,
|
||||
|
||||
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<Type<'db>> {
|
||||
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<TypeVarInstance<'db>>,
|
||||
) {
|
||||
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<TypeVarInstance<'db>>,
|
||||
) {
|
||||
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.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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()
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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<Type<'db>>,
|
||||
) -> Option<Self> {
|
||||
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<TypeVarInstance<'db>, UnionBuilder<'db>>,
|
||||
types: FxHashMap<TypeVarInstance<'db>, 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
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 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)
|
||||
{
|
||||
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::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::GetProtocolMembers => {
|
||||
if let [Some(Type::ClassLiteral(class))] =
|
||||
overload.parameter_types()
|
||||
{
|
||||
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),
|
||||
));
|
||||
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;
|
||||
};
|
||||
Type::ClassLiteral(class) => {
|
||||
let Some(known_class) = class.known(self.db()) else {
|
||||
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;
|
||||
};
|
||||
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 bound_super = BoundSuperType::build(
|
||||
self.db(),
|
||||
enclosing_class,
|
||||
first_param,
|
||||
)
|
||||
.unwrap_or_else(|err| {
|
||||
err.report_diagnostic(
|
||||
&self.context,
|
||||
call_expression.into(),
|
||||
);
|
||||
Type::unknown()
|
||||
});
|
||||
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;
|
||||
};
|
||||
|
||||
overload.set_return_type(bound_super);
|
||||
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::<Box<_>>(),
|
||||
);
|
||||
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,
|
||||
)),
|
||||
));
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
[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);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
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();
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<TypeVarInstance<'db>>,
|
||||
) {
|
||||
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
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)",
|
||||
|
||||
Reference in New Issue
Block a user