Skip to content

Commit 2809aa4

Browse files
committed
[red-knot] Disallow more invalid type expressions
1 parent cf83584 commit 2809aa4

File tree

3 files changed

+131
-40
lines changed

3 files changed

+131
-40
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Tests for invalid types in type expressions
2+
3+
## Invalid types are rejected
4+
5+
Many types are illegal in the context of a type expression:
6+
7+
```py
8+
import typing
9+
from knot_extensions import AlwaysTruthy, AlwaysFalsy
10+
from typing_extensions import Literal, Never
11+
12+
def _(
13+
a: type[int],
14+
b: AlwaysTruthy,
15+
c: AlwaysFalsy,
16+
d: Literal[True],
17+
e: Literal["bar"],
18+
f: Literal[b"foo"],
19+
g: tuple[int, str],
20+
h: Never,
21+
):
22+
def foo(): ...
23+
def invalid(
24+
i: a, # error: [invalid-type-form] "Variable of type `type[int]` is not allowed in a type expression"
25+
j: b, # error: [invalid-type-form]
26+
k: c, # error: [invalid-type-form]
27+
l: d, # error: [invalid-type-form]
28+
m: e, # error: [invalid-type-form]
29+
n: f, # error: [invalid-type-form]
30+
o: g, # error: [invalid-type-form]
31+
p: h, # error: [invalid-type-form]
32+
q: typing, # error: [invalid-type-form]
33+
r: foo, # error: [invalid-type-form]
34+
):
35+
reveal_type(i) # revealed: Unknown
36+
reveal_type(j) # revealed: Unknown
37+
reveal_type(k) # revealed: Unknown
38+
reveal_type(l) # revealed: Unknown
39+
reveal_type(m) # revealed: Unknown
40+
reveal_type(n) # revealed: Unknown
41+
reveal_type(o) # revealed: Unknown
42+
reveal_type(p) # revealed: Unknown
43+
reveal_type(q) # revealed: Unknown
44+
reveal_type(r) # revealed: Unknown
45+
```

crates/red_knot_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def f(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]:
1818
# TODO: should understand the annotation
1919
reveal_type(args) # revealed: tuple
2020

21-
reveal_type(Alias) # revealed: @Todo(Unsupported or invalid type in a type expression)
21+
reveal_type(Alias) # revealed: @Todo(Invalid or unsupported `KnownInstanceType` in `Type::to_type_expression`)
2222

2323
def g() -> TypeGuard[int]: ...
2424
def h() -> TypeIs[int]: ...
@@ -33,7 +33,7 @@ def i(callback: Callable[Concatenate[int, P], R_co], *args: P.args, **kwargs: P.
3333

3434
class Foo:
3535
def method(self, x: Self):
36-
reveal_type(x) # revealed: @Todo(Unsupported or invalid type in a type expression)
36+
reveal_type(x) # revealed: @Todo(Invalid or unsupported `KnownInstanceType` in `Type::to_type_expression`)
3737
```
3838

3939
## Inheritance

crates/red_knot_python_semantic/src/types.rs

+84-38
Original file line numberDiff line numberDiff line change
@@ -2470,32 +2470,45 @@ impl<'db> Type<'db> {
24702470
match self {
24712471
// Special cases for `float` and `complex`
24722472
// https://typing.readthedocs.io/en/latest/spec/special-types.html#special-cases-for-float-and-complex
2473-
Type::ClassLiteral(ClassLiteralType { class })
2474-
if class.is_known(db, KnownClass::Float) =>
2475-
{
2476-
Ok(UnionType::from_elements(
2477-
db,
2478-
[
2479-
KnownClass::Int.to_instance(db),
2480-
KnownClass::Float.to_instance(db),
2481-
],
2482-
))
2483-
}
2484-
Type::ClassLiteral(ClassLiteralType { class })
2485-
if class.is_known(db, KnownClass::Complex) =>
2486-
{
2487-
Ok(UnionType::from_elements(
2488-
db,
2489-
[
2490-
KnownClass::Int.to_instance(db),
2491-
KnownClass::Float.to_instance(db),
2492-
KnownClass::Complex.to_instance(db),
2493-
],
2494-
))
2473+
Type::ClassLiteral(ClassLiteralType { class }) => {
2474+
let ty = match class.known(db) {
2475+
Some(KnownClass::Complex) => UnionType::from_elements(
2476+
db,
2477+
[
2478+
KnownClass::Int.to_instance(db),
2479+
KnownClass::Float.to_instance(db),
2480+
KnownClass::Complex.to_instance(db),
2481+
],
2482+
),
2483+
Some(KnownClass::Float) => UnionType::from_elements(
2484+
db,
2485+
[
2486+
KnownClass::Int.to_instance(db),
2487+
KnownClass::Float.to_instance(db),
2488+
],
2489+
),
2490+
_ => Type::instance(*class),
2491+
};
2492+
Ok(ty)
24952493
}
2496-
// In a type expression, a bare `type` is interpreted as "instance of `type`", which is
2497-
// equivalent to `type[object]`.
2498-
Type::ClassLiteral(_) | Type::SubclassOf(_) => Ok(self.to_instance(db)),
2494+
Type::SubclassOf(_)
2495+
| Type::BooleanLiteral(_)
2496+
| Type::BytesLiteral(_)
2497+
| Type::AlwaysTruthy
2498+
| Type::AlwaysFalsy
2499+
| Type::SliceLiteral(_)
2500+
| Type::IntLiteral(_)
2501+
| Type::LiteralString
2502+
| Type::ModuleLiteral(_)
2503+
| Type::StringLiteral(_)
2504+
| Type::Tuple(_)
2505+
| Type::Callable(_)
2506+
| Type::Never
2507+
| Type::FunctionLiteral(_) => Err(InvalidTypeExpressionError {
2508+
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::InvalidType(*self)],
2509+
fallback_type: Type::unknown(),
2510+
}),
2511+
24992512
// We treat `typing.Type` exactly the same as `builtins.type`:
25002513
Type::KnownInstance(KnownInstanceType::Type) => Ok(KnownClass::Type.to_instance(db)),
25012514
Type::KnownInstance(KnownInstanceType::Tuple) => Ok(KnownClass::Tuple.to_instance(db)),
@@ -2556,7 +2569,6 @@ impl<'db> Type<'db> {
25562569
}
25572570
Type::KnownInstance(KnownInstanceType::LiteralString) => Ok(Type::LiteralString),
25582571
Type::KnownInstance(KnownInstanceType::Any) => Ok(Type::any()),
2559-
// TODO: Should emit a diagnostic
25602572
Type::KnownInstance(KnownInstanceType::Annotated) => Err(InvalidTypeExpressionError {
25612573
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::BareAnnotated],
25622574
fallback_type: Type::unknown(),
@@ -2580,9 +2592,13 @@ impl<'db> Type<'db> {
25802592
Type::KnownInstance(KnownInstanceType::Unknown) => Ok(Type::unknown()),
25812593
Type::KnownInstance(KnownInstanceType::AlwaysTruthy) => Ok(Type::AlwaysTruthy),
25822594
Type::KnownInstance(KnownInstanceType::AlwaysFalsy) => Ok(Type::AlwaysFalsy),
2583-
_ => Ok(todo_type!(
2584-
"Unsupported or invalid type in a type expression"
2595+
Type::Instance(_) => Ok(todo_type!(
2596+
"Invalid or unsupported `Instance` in `Type::to_type_expression`"
2597+
)),
2598+
Type::KnownInstance(_) => Ok(todo_type!(
2599+
"Invalid or unsupported `KnownInstanceType` in `Type::to_type_expression`"
25852600
)),
2601+
Type::Intersection(_) => Ok(todo_type!("Type::Intersection.in_type_expression")),
25862602
}
25872603
}
25882604

@@ -2815,7 +2831,7 @@ impl<'db> From<Type<'db>> for TypeAndQualifiers<'db> {
28152831
#[derive(Debug, PartialEq, Eq)]
28162832
pub struct InvalidTypeExpressionError<'db> {
28172833
fallback_type: Type<'db>,
2818-
invalid_expressions: smallvec::SmallVec<[InvalidTypeExpression; 1]>,
2834+
invalid_expressions: smallvec::SmallVec<[InvalidTypeExpression<'db>; 1]>,
28192835
}
28202836

28212837
impl<'db> InvalidTypeExpressionError<'db> {
@@ -2825,15 +2841,19 @@ impl<'db> InvalidTypeExpressionError<'db> {
28252841
invalid_expressions,
28262842
} = self;
28272843
for error in invalid_expressions {
2828-
context.report_lint(&INVALID_TYPE_FORM, node, format_args!("{}", error.reason()));
2844+
context.report_lint(
2845+
&INVALID_TYPE_FORM,
2846+
node,
2847+
format_args!("{}", error.reason(context.db())),
2848+
);
28292849
}
28302850
fallback_type
28312851
}
28322852
}
28332853

28342854
/// Enumeration of various types that are invalid in type-expression contexts
28352855
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
2836-
enum InvalidTypeExpression {
2856+
enum InvalidTypeExpression<'db> {
28372857
/// `x: Annotated` is invalid as an annotation
28382858
BareAnnotated,
28392859
/// `x: Literal` is invalid as an annotation
@@ -2842,16 +2862,42 @@ enum InvalidTypeExpression {
28422862
ClassVarInTypeExpression,
28432863
/// The `Final` type qualifier was used in a type expression
28442864
FinalInTypeExpression,
2865+
/// Some types are always invalid in type expressions
2866+
InvalidType(Type<'db>),
28452867
}
28462868

2847-
impl InvalidTypeExpression {
2848-
const fn reason(self) -> &'static str {
2849-
match self {
2850-
Self::BareAnnotated => "`Annotated` requires at least two arguments when used in an annotation or type expression",
2851-
Self::BareLiteral => "`Literal` requires at least one argument when used in a type expression",
2852-
Self::ClassVarInTypeExpression => "Type qualifier `typing.ClassVar` is not allowed in type expressions (only in annotation expressions)",
2853-
Self::FinalInTypeExpression => "Type qualifier `typing.Final` is not allowed in type expressions (only in annotation expressions)",
2869+
impl<'db> InvalidTypeExpression<'db> {
2870+
const fn reason(self, db: &'db dyn Db) -> impl std::fmt::Display + 'db {
2871+
struct Display<'db> {
2872+
error: InvalidTypeExpression<'db>,
2873+
db: &'db dyn Db,
28542874
}
2875+
2876+
impl std::fmt::Display for Display<'_> {
2877+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2878+
match self.error {
2879+
InvalidTypeExpression::BareAnnotated => f.write_str(
2880+
"`Annotated` requires at least two arguments when used in an annotation or type expression"
2881+
),
2882+
InvalidTypeExpression::BareLiteral => f.write_str(
2883+
"`Literal` requires at least one argument when used in a type expression"
2884+
),
2885+
InvalidTypeExpression::ClassVarInTypeExpression => f.write_str(
2886+
"Type qualifier `typing.ClassVar` is not allowed in type expressions (only in annotation expressions)"
2887+
),
2888+
InvalidTypeExpression::FinalInTypeExpression => f.write_str(
2889+
"Type qualifier `typing.Final` is not allowed in type expressions (only in annotation expressions)"
2890+
),
2891+
InvalidTypeExpression::InvalidType(ty) => write!(
2892+
f,
2893+
"Variable of type `{ty}` is not allowed in a type expression",
2894+
ty = ty.display(self.db)
2895+
),
2896+
}
2897+
}
2898+
}
2899+
2900+
Display { error: self, db }
28552901
}
28562902
}
28572903

0 commit comments

Comments
 (0)