Compare commits

...

1 Commits

Author SHA1 Message Date
Dhruv Manilawala
9ea31a8923 TypeVarInstance and intersection of callables 2025-04-28 16:27:17 -05:00
22 changed files with 1453 additions and 823 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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"]
```

View File

@@ -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: ...
```

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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: ...
| ^
|
```

View File

@@ -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)

View File

@@ -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")
```

View File

@@ -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);

View File

@@ -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,

View File

@@ -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

View File

@@ -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()
));
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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",

View File

@@ -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, &parameters, 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

View File

@@ -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())

View File

@@ -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)",