mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-02 06:41:23 +00:00
[red-knot] Disallow more invalid type expressions (#16427)
This commit is contained in:
parent
091d0af2ab
commit
09d0b227fb
3 changed files with 131 additions and 40 deletions
|
@ -0,0 +1,45 @@
|
||||||
|
# Tests for invalid types in type expressions
|
||||||
|
|
||||||
|
## Invalid types are rejected
|
||||||
|
|
||||||
|
Many types are illegal in the context of a type expression:
|
||||||
|
|
||||||
|
```py
|
||||||
|
import typing
|
||||||
|
from knot_extensions import AlwaysTruthy, AlwaysFalsy
|
||||||
|
from typing_extensions import Literal, Never
|
||||||
|
|
||||||
|
def _(
|
||||||
|
a: type[int],
|
||||||
|
b: AlwaysTruthy,
|
||||||
|
c: AlwaysFalsy,
|
||||||
|
d: Literal[True],
|
||||||
|
e: Literal["bar"],
|
||||||
|
f: Literal[b"foo"],
|
||||||
|
g: tuple[int, str],
|
||||||
|
h: Never,
|
||||||
|
):
|
||||||
|
def foo(): ...
|
||||||
|
def invalid(
|
||||||
|
i: a, # error: [invalid-type-form] "Variable of type `type[int]` is not allowed in a type expression"
|
||||||
|
j: b, # error: [invalid-type-form]
|
||||||
|
k: c, # error: [invalid-type-form]
|
||||||
|
l: d, # error: [invalid-type-form]
|
||||||
|
m: e, # error: [invalid-type-form]
|
||||||
|
n: f, # error: [invalid-type-form]
|
||||||
|
o: g, # error: [invalid-type-form]
|
||||||
|
p: h, # error: [invalid-type-form]
|
||||||
|
q: typing, # error: [invalid-type-form]
|
||||||
|
r: foo, # error: [invalid-type-form]
|
||||||
|
):
|
||||||
|
reveal_type(i) # revealed: Unknown
|
||||||
|
reveal_type(j) # revealed: Unknown
|
||||||
|
reveal_type(k) # revealed: Unknown
|
||||||
|
reveal_type(l) # revealed: Unknown
|
||||||
|
reveal_type(m) # revealed: Unknown
|
||||||
|
reveal_type(n) # revealed: Unknown
|
||||||
|
reveal_type(o) # revealed: Unknown
|
||||||
|
reveal_type(p) # revealed: Unknown
|
||||||
|
reveal_type(q) # revealed: Unknown
|
||||||
|
reveal_type(r) # revealed: Unknown
|
||||||
|
```
|
|
@ -18,7 +18,7 @@ def f(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]:
|
||||||
# TODO: should understand the annotation
|
# TODO: should understand the annotation
|
||||||
reveal_type(args) # revealed: tuple
|
reveal_type(args) # revealed: tuple
|
||||||
|
|
||||||
reveal_type(Alias) # revealed: @Todo(Unsupported or invalid type in a type expression)
|
reveal_type(Alias) # revealed: @Todo(Invalid or unsupported `KnownInstanceType` in `Type::to_type_expression`)
|
||||||
|
|
||||||
def g() -> TypeGuard[int]: ...
|
def g() -> TypeGuard[int]: ...
|
||||||
def h() -> TypeIs[int]: ...
|
def h() -> TypeIs[int]: ...
|
||||||
|
@ -33,7 +33,7 @@ def i(callback: Callable[Concatenate[int, P], R_co], *args: P.args, **kwargs: P.
|
||||||
|
|
||||||
class Foo:
|
class Foo:
|
||||||
def method(self, x: Self):
|
def method(self, x: Self):
|
||||||
reveal_type(x) # revealed: @Todo(Unsupported or invalid type in a type expression)
|
reveal_type(x) # revealed: @Todo(Invalid or unsupported `KnownInstanceType` in `Type::to_type_expression`)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Inheritance
|
## Inheritance
|
||||||
|
|
|
@ -2470,32 +2470,45 @@ impl<'db> Type<'db> {
|
||||||
match self {
|
match self {
|
||||||
// Special cases for `float` and `complex`
|
// Special cases for `float` and `complex`
|
||||||
// https://typing.readthedocs.io/en/latest/spec/special-types.html#special-cases-for-float-and-complex
|
// https://typing.readthedocs.io/en/latest/spec/special-types.html#special-cases-for-float-and-complex
|
||||||
Type::ClassLiteral(ClassLiteralType { class })
|
Type::ClassLiteral(ClassLiteralType { class }) => {
|
||||||
if class.is_known(db, KnownClass::Float) =>
|
let ty = match class.known(db) {
|
||||||
{
|
Some(KnownClass::Complex) => UnionType::from_elements(
|
||||||
Ok(UnionType::from_elements(
|
db,
|
||||||
db,
|
[
|
||||||
[
|
KnownClass::Int.to_instance(db),
|
||||||
KnownClass::Int.to_instance(db),
|
KnownClass::Float.to_instance(db),
|
||||||
KnownClass::Float.to_instance(db),
|
KnownClass::Complex.to_instance(db),
|
||||||
],
|
],
|
||||||
))
|
),
|
||||||
|
Some(KnownClass::Float) => UnionType::from_elements(
|
||||||
|
db,
|
||||||
|
[
|
||||||
|
KnownClass::Int.to_instance(db),
|
||||||
|
KnownClass::Float.to_instance(db),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
_ => Type::instance(*class),
|
||||||
|
};
|
||||||
|
Ok(ty)
|
||||||
}
|
}
|
||||||
Type::ClassLiteral(ClassLiteralType { class })
|
Type::SubclassOf(_)
|
||||||
if class.is_known(db, KnownClass::Complex) =>
|
| Type::BooleanLiteral(_)
|
||||||
{
|
| Type::BytesLiteral(_)
|
||||||
Ok(UnionType::from_elements(
|
| Type::AlwaysTruthy
|
||||||
db,
|
| Type::AlwaysFalsy
|
||||||
[
|
| Type::SliceLiteral(_)
|
||||||
KnownClass::Int.to_instance(db),
|
| Type::IntLiteral(_)
|
||||||
KnownClass::Float.to_instance(db),
|
| Type::LiteralString
|
||||||
KnownClass::Complex.to_instance(db),
|
| Type::ModuleLiteral(_)
|
||||||
],
|
| Type::StringLiteral(_)
|
||||||
))
|
| Type::Tuple(_)
|
||||||
}
|
| Type::Callable(_)
|
||||||
// In a type expression, a bare `type` is interpreted as "instance of `type`", which is
|
| Type::Never
|
||||||
// equivalent to `type[object]`.
|
| Type::FunctionLiteral(_) => Err(InvalidTypeExpressionError {
|
||||||
Type::ClassLiteral(_) | Type::SubclassOf(_) => Ok(self.to_instance(db)),
|
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::InvalidType(*self)],
|
||||||
|
fallback_type: Type::unknown(),
|
||||||
|
}),
|
||||||
|
|
||||||
// We treat `typing.Type` exactly the same as `builtins.type`:
|
// We treat `typing.Type` exactly the same as `builtins.type`:
|
||||||
Type::KnownInstance(KnownInstanceType::Type) => Ok(KnownClass::Type.to_instance(db)),
|
Type::KnownInstance(KnownInstanceType::Type) => Ok(KnownClass::Type.to_instance(db)),
|
||||||
Type::KnownInstance(KnownInstanceType::Tuple) => Ok(KnownClass::Tuple.to_instance(db)),
|
Type::KnownInstance(KnownInstanceType::Tuple) => Ok(KnownClass::Tuple.to_instance(db)),
|
||||||
|
@ -2556,7 +2569,6 @@ impl<'db> Type<'db> {
|
||||||
}
|
}
|
||||||
Type::KnownInstance(KnownInstanceType::LiteralString) => Ok(Type::LiteralString),
|
Type::KnownInstance(KnownInstanceType::LiteralString) => Ok(Type::LiteralString),
|
||||||
Type::KnownInstance(KnownInstanceType::Any) => Ok(Type::any()),
|
Type::KnownInstance(KnownInstanceType::Any) => Ok(Type::any()),
|
||||||
// TODO: Should emit a diagnostic
|
|
||||||
Type::KnownInstance(KnownInstanceType::Annotated) => Err(InvalidTypeExpressionError {
|
Type::KnownInstance(KnownInstanceType::Annotated) => Err(InvalidTypeExpressionError {
|
||||||
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::BareAnnotated],
|
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::BareAnnotated],
|
||||||
fallback_type: Type::unknown(),
|
fallback_type: Type::unknown(),
|
||||||
|
@ -2580,9 +2592,13 @@ impl<'db> Type<'db> {
|
||||||
Type::KnownInstance(KnownInstanceType::Unknown) => Ok(Type::unknown()),
|
Type::KnownInstance(KnownInstanceType::Unknown) => Ok(Type::unknown()),
|
||||||
Type::KnownInstance(KnownInstanceType::AlwaysTruthy) => Ok(Type::AlwaysTruthy),
|
Type::KnownInstance(KnownInstanceType::AlwaysTruthy) => Ok(Type::AlwaysTruthy),
|
||||||
Type::KnownInstance(KnownInstanceType::AlwaysFalsy) => Ok(Type::AlwaysFalsy),
|
Type::KnownInstance(KnownInstanceType::AlwaysFalsy) => Ok(Type::AlwaysFalsy),
|
||||||
_ => Ok(todo_type!(
|
Type::KnownInstance(_) => Ok(todo_type!(
|
||||||
"Unsupported or invalid type in a type expression"
|
"Invalid or unsupported `KnownInstanceType` in `Type::to_type_expression`"
|
||||||
)),
|
)),
|
||||||
|
Type::Instance(_) => Ok(todo_type!(
|
||||||
|
"Invalid or unsupported `Instance` in `Type::to_type_expression`"
|
||||||
|
)),
|
||||||
|
Type::Intersection(_) => Ok(todo_type!("Type::Intersection.in_type_expression")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2815,7 +2831,7 @@ impl<'db> From<Type<'db>> for TypeAndQualifiers<'db> {
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct InvalidTypeExpressionError<'db> {
|
pub struct InvalidTypeExpressionError<'db> {
|
||||||
fallback_type: Type<'db>,
|
fallback_type: Type<'db>,
|
||||||
invalid_expressions: smallvec::SmallVec<[InvalidTypeExpression; 1]>,
|
invalid_expressions: smallvec::SmallVec<[InvalidTypeExpression<'db>; 1]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> InvalidTypeExpressionError<'db> {
|
impl<'db> InvalidTypeExpressionError<'db> {
|
||||||
|
@ -2825,7 +2841,11 @@ impl<'db> InvalidTypeExpressionError<'db> {
|
||||||
invalid_expressions,
|
invalid_expressions,
|
||||||
} = self;
|
} = self;
|
||||||
for error in invalid_expressions {
|
for error in invalid_expressions {
|
||||||
context.report_lint(&INVALID_TYPE_FORM, node, format_args!("{}", error.reason()));
|
context.report_lint(
|
||||||
|
&INVALID_TYPE_FORM,
|
||||||
|
node,
|
||||||
|
format_args!("{}", error.reason(context.db())),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
fallback_type
|
fallback_type
|
||||||
}
|
}
|
||||||
|
@ -2833,7 +2853,7 @@ impl<'db> InvalidTypeExpressionError<'db> {
|
||||||
|
|
||||||
/// Enumeration of various types that are invalid in type-expression contexts
|
/// Enumeration of various types that are invalid in type-expression contexts
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
enum InvalidTypeExpression {
|
enum InvalidTypeExpression<'db> {
|
||||||
/// `x: Annotated` is invalid as an annotation
|
/// `x: Annotated` is invalid as an annotation
|
||||||
BareAnnotated,
|
BareAnnotated,
|
||||||
/// `x: Literal` is invalid as an annotation
|
/// `x: Literal` is invalid as an annotation
|
||||||
|
@ -2842,16 +2862,42 @@ enum InvalidTypeExpression {
|
||||||
ClassVarInTypeExpression,
|
ClassVarInTypeExpression,
|
||||||
/// The `Final` type qualifier was used in a type expression
|
/// The `Final` type qualifier was used in a type expression
|
||||||
FinalInTypeExpression,
|
FinalInTypeExpression,
|
||||||
|
/// Some types are always invalid in type expressions
|
||||||
|
InvalidType(Type<'db>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InvalidTypeExpression {
|
impl<'db> InvalidTypeExpression<'db> {
|
||||||
const fn reason(self) -> &'static str {
|
const fn reason(self, db: &'db dyn Db) -> impl std::fmt::Display + 'db {
|
||||||
match self {
|
struct Display<'db> {
|
||||||
Self::BareAnnotated => "`Annotated` requires at least two arguments when used in an annotation or type expression",
|
error: InvalidTypeExpression<'db>,
|
||||||
Self::BareLiteral => "`Literal` requires at least one argument when used in a type expression",
|
db: &'db dyn Db,
|
||||||
Self::ClassVarInTypeExpression => "Type qualifier `typing.ClassVar` is not allowed in type expressions (only in annotation expressions)",
|
|
||||||
Self::FinalInTypeExpression => "Type qualifier `typing.Final` is not allowed in type expressions (only in annotation expressions)",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Display<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self.error {
|
||||||
|
InvalidTypeExpression::BareAnnotated => f.write_str(
|
||||||
|
"`Annotated` requires at least two arguments when used in an annotation or type expression"
|
||||||
|
),
|
||||||
|
InvalidTypeExpression::BareLiteral => f.write_str(
|
||||||
|
"`Literal` requires at least one argument when used in a type expression"
|
||||||
|
),
|
||||||
|
InvalidTypeExpression::ClassVarInTypeExpression => f.write_str(
|
||||||
|
"Type qualifier `typing.ClassVar` is not allowed in type expressions (only in annotation expressions)"
|
||||||
|
),
|
||||||
|
InvalidTypeExpression::FinalInTypeExpression => f.write_str(
|
||||||
|
"Type qualifier `typing.Final` is not allowed in type expressions (only in annotation expressions)"
|
||||||
|
),
|
||||||
|
InvalidTypeExpression::InvalidType(ty) => write!(
|
||||||
|
f,
|
||||||
|
"Variable of type `{ty}` is not allowed in a type expression",
|
||||||
|
ty = ty.display(self.db)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Display { error: self, db }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue