Skip to content

Commit 4ed93b4

Browse files
Show more precise messages in invalid type expressions (#16850)
## Summary Some error messages were not very specific; this PR improves them ## Test Plan New mdtests added; existing mdtests tweaked
1 parent 98fdc0e commit 4ed93b4

File tree

4 files changed

+171
-122
lines changed

4 files changed

+171
-122
lines changed

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ It is invalid to parameterize `Annotated` with less than two arguments.
2929
```py
3030
from typing_extensions import Annotated
3131

32-
# error: [invalid-type-form] "`Annotated` requires at least two arguments when used in an annotation or type expression"
32+
# error: [invalid-type-form] "`typing.Annotated` requires at least two arguments when used in a type expression"
3333
def _(x: Annotated):
3434
reveal_type(x) # revealed: Unknown
3535

@@ -39,11 +39,11 @@ def _(flag: bool):
3939
else:
4040
X = bool
4141

42-
# error: [invalid-type-form] "`Annotated` requires at least two arguments when used in an annotation or type expression"
42+
# error: [invalid-type-form] "`typing.Annotated` requires at least two arguments when used in a type expression"
4343
def f(y: X):
4444
reveal_type(y) # revealed: Unknown | bool
4545

46-
# error: [invalid-type-form] "`Annotated` requires at least two arguments when used in an annotation or type expression"
46+
# error: [invalid-type-form] "`typing.Annotated` requires at least two arguments when used in a type expression"
4747
def _(x: Annotated | bool):
4848
reveal_type(x) # revealed: Unknown | bool
4949

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

+21-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(Invalid or unsupported `KnownInstanceType` in `Type::to_type_expression`)
21+
reveal_type(Alias) # revealed: @Todo(Support for `typing.TypeAlias`)
2222

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

3636
class Foo:
3737
def method(self, x: Self):
38-
reveal_type(x) # revealed: @Todo(Invalid or unsupported `KnownInstanceType` in `Type::to_type_expression`)
38+
reveal_type(x) # revealed: @Todo(Support for `typing.Self`)
39+
```
40+
41+
## Type expressions
42+
43+
One thing that is supported is error messages for using special forms in type expressions.
44+
45+
```py
46+
from typing_extensions import Unpack, TypeGuard, TypeIs, Concatenate
47+
48+
def _(
49+
a: Unpack, # error: [invalid-type-form] "`typing.Unpack` requires exactly one argument when used in a type expression"
50+
b: TypeGuard, # error: [invalid-type-form] "`typing.TypeGuard` requires exactly one argument when used in a type expression"
51+
c: TypeIs, # error: [invalid-type-form] "`typing.TypeIs` requires exactly one argument when used in a type expression"
52+
d: Concatenate, # error: [invalid-type-form] "`typing.Concatenate` requires at least two arguments when used in a type expression"
53+
) -> None:
54+
reveal_type(a) # revealed: Unknown
55+
reveal_type(b) # revealed: Unknown
56+
reveal_type(c) # revealed: Unknown
57+
reveal_type(d) # revealed: Unknown
3958
```
4059

4160
## Inheritance

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

+28-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Unsupported type qualifiers
22

3-
## Not yet supported
3+
## Not yet fully supported
44

55
Several type qualifiers are unsupported by red-knot currently. However, we also don't emit
66
false-positive errors if you use one in an annotation:
@@ -19,6 +19,33 @@ class Bar(TypedDict):
1919
z: ReadOnly[bytes]
2020
```
2121

22+
## Type expressions
23+
24+
One thing that is supported is error messages for using type qualifiers in type expressions.
25+
26+
```py
27+
from typing_extensions import Final, ClassVar, Required, NotRequired, ReadOnly
28+
29+
def _(
30+
a: (
31+
Final # error: [invalid-type-form] "Type qualifier `typing.Final` is not allowed in type expressions (only in annotation expressions)"
32+
| int
33+
),
34+
b: (
35+
ClassVar # error: [invalid-type-form] "Type qualifier `typing.ClassVar` is not allowed in type expressions (only in annotation expressions)"
36+
| int
37+
),
38+
c: Required, # error: [invalid-type-form] "Type qualifier `typing.Required` is not allowed in type expressions (only in annotation expressions, and only with exactly one argument)"
39+
d: NotRequired, # error: [invalid-type-form] "Type qualifier `typing.NotRequired` is not allowed in type expressions (only in annotation expressions, and only with exactly one argument)"
40+
e: ReadOnly, # error: [invalid-type-form] "Type qualifier `typing.ReadOnly` is not allowed in type expressions (only in annotation expressions, and only with exactly one argument)"
41+
) -> None:
42+
reveal_type(a) # revealed: Unknown | int
43+
reveal_type(b) # revealed: Unknown | int
44+
reveal_type(c) # revealed: Unknown
45+
reveal_type(d) # revealed: Unknown
46+
reveal_type(e) # revealed: Unknown
47+
```
48+
2249
## Inheritance
2350

2451
You can't inherit from a type qualifier.

crates/red_knot_python_semantic/src/types.rs

+119-116
Original file line numberDiff line numberDiff line change
@@ -3182,6 +3182,7 @@ impl<'db> Type<'db> {
31823182
};
31833183
Ok(ty)
31843184
}
3185+
31853186
Type::SubclassOf(_)
31863187
| Type::BooleanLiteral(_)
31873188
| Type::BytesLiteral(_)
@@ -3200,30 +3201,95 @@ impl<'db> Type<'db> {
32003201
fallback_type: Type::unknown(),
32013202
}),
32023203

3203-
// We treat `typing.Type` exactly the same as `builtins.type`:
3204-
Type::KnownInstance(KnownInstanceType::Type) => Ok(KnownClass::Type.to_instance(db)),
3205-
Type::KnownInstance(KnownInstanceType::Tuple) => Ok(KnownClass::Tuple.to_instance(db)),
3204+
Type::KnownInstance(known_instance) => match known_instance {
3205+
KnownInstanceType::TypeAliasType(alias) => Ok(alias.value_type(db)),
3206+
KnownInstanceType::Never | KnownInstanceType::NoReturn => Ok(Type::Never),
3207+
KnownInstanceType::LiteralString => Ok(Type::LiteralString),
3208+
KnownInstanceType::Any => Ok(Type::any()),
3209+
KnownInstanceType::Unknown => Ok(Type::unknown()),
3210+
KnownInstanceType::AlwaysTruthy => Ok(Type::AlwaysTruthy),
3211+
KnownInstanceType::AlwaysFalsy => Ok(Type::AlwaysFalsy),
3212+
3213+
// We treat `typing.Type` exactly the same as `builtins.type`:
3214+
KnownInstanceType::Type => Ok(KnownClass::Type.to_instance(db)),
3215+
KnownInstanceType::Tuple => Ok(KnownClass::Tuple.to_instance(db)),
3216+
3217+
// Legacy `typing` aliases
3218+
KnownInstanceType::List => Ok(KnownClass::List.to_instance(db)),
3219+
KnownInstanceType::Dict => Ok(KnownClass::Dict.to_instance(db)),
3220+
KnownInstanceType::Set => Ok(KnownClass::Set.to_instance(db)),
3221+
KnownInstanceType::FrozenSet => Ok(KnownClass::FrozenSet.to_instance(db)),
3222+
KnownInstanceType::ChainMap => Ok(KnownClass::ChainMap.to_instance(db)),
3223+
KnownInstanceType::Counter => Ok(KnownClass::Counter.to_instance(db)),
3224+
KnownInstanceType::DefaultDict => Ok(KnownClass::DefaultDict.to_instance(db)),
3225+
KnownInstanceType::Deque => Ok(KnownClass::Deque.to_instance(db)),
3226+
KnownInstanceType::OrderedDict => Ok(KnownClass::OrderedDict.to_instance(db)),
3227+
3228+
// TODO map this to a new `Type::TypeVar` variant
3229+
KnownInstanceType::TypeVar(_) => Ok(*self),
32063230

3207-
// Legacy `typing` aliases
3208-
Type::KnownInstance(KnownInstanceType::List) => Ok(KnownClass::List.to_instance(db)),
3209-
Type::KnownInstance(KnownInstanceType::Dict) => Ok(KnownClass::Dict.to_instance(db)),
3210-
Type::KnownInstance(KnownInstanceType::Set) => Ok(KnownClass::Set.to_instance(db)),
3211-
Type::KnownInstance(KnownInstanceType::FrozenSet) => {
3212-
Ok(KnownClass::FrozenSet.to_instance(db))
3213-
}
3214-
Type::KnownInstance(KnownInstanceType::ChainMap) => {
3215-
Ok(KnownClass::ChainMap.to_instance(db))
3216-
}
3217-
Type::KnownInstance(KnownInstanceType::Counter) => {
3218-
Ok(KnownClass::Counter.to_instance(db))
3219-
}
3220-
Type::KnownInstance(KnownInstanceType::DefaultDict) => {
3221-
Ok(KnownClass::DefaultDict.to_instance(db))
3222-
}
3223-
Type::KnownInstance(KnownInstanceType::Deque) => Ok(KnownClass::Deque.to_instance(db)),
3224-
Type::KnownInstance(KnownInstanceType::OrderedDict) => {
3225-
Ok(KnownClass::OrderedDict.to_instance(db))
3226-
}
3231+
// TODO: Use an opt-in rule for a bare `Callable`
3232+
KnownInstanceType::Callable => Ok(Type::Callable(CallableType::General(
3233+
GeneralCallableType::unknown(db),
3234+
))),
3235+
3236+
KnownInstanceType::TypingSelf => Ok(todo_type!("Support for `typing.Self`")),
3237+
KnownInstanceType::TypeAlias => Ok(todo_type!("Support for `typing.TypeAlias`")),
3238+
3239+
KnownInstanceType::Protocol => Err(InvalidTypeExpressionError {
3240+
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::Protocol],
3241+
fallback_type: Type::unknown(),
3242+
}),
3243+
3244+
KnownInstanceType::Literal
3245+
| KnownInstanceType::Union
3246+
| KnownInstanceType::Intersection => Err(InvalidTypeExpressionError {
3247+
invalid_expressions: smallvec::smallvec![
3248+
InvalidTypeExpression::RequiresArguments(*self)
3249+
],
3250+
fallback_type: Type::unknown(),
3251+
}),
3252+
3253+
KnownInstanceType::Optional
3254+
| KnownInstanceType::Not
3255+
| KnownInstanceType::TypeOf
3256+
| KnownInstanceType::TypeIs
3257+
| KnownInstanceType::TypeGuard
3258+
| KnownInstanceType::Unpack
3259+
| KnownInstanceType::CallableTypeFromFunction => Err(InvalidTypeExpressionError {
3260+
invalid_expressions: smallvec::smallvec![
3261+
InvalidTypeExpression::RequiresOneArgument(*self)
3262+
],
3263+
fallback_type: Type::unknown(),
3264+
}),
3265+
3266+
KnownInstanceType::Annotated | KnownInstanceType::Concatenate => {
3267+
Err(InvalidTypeExpressionError {
3268+
invalid_expressions: smallvec::smallvec![
3269+
InvalidTypeExpression::RequiresTwoArguments(*self)
3270+
],
3271+
fallback_type: Type::unknown(),
3272+
})
3273+
}
3274+
3275+
KnownInstanceType::ClassVar | KnownInstanceType::Final => {
3276+
Err(InvalidTypeExpressionError {
3277+
invalid_expressions: smallvec::smallvec![
3278+
InvalidTypeExpression::TypeQualifier(*known_instance)
3279+
],
3280+
fallback_type: Type::unknown(),
3281+
})
3282+
}
3283+
3284+
KnownInstanceType::ReadOnly
3285+
| KnownInstanceType::NotRequired
3286+
| KnownInstanceType::Required => Err(InvalidTypeExpressionError {
3287+
invalid_expressions: smallvec::smallvec![
3288+
InvalidTypeExpression::TypeQualifierRequiresOneArgument(*known_instance)
3289+
],
3290+
fallback_type: Type::unknown(),
3291+
}),
3292+
},
32273293

32283294
Type::Union(union) => {
32293295
let mut builder = UnionBuilder::new(db);
@@ -3249,85 +3315,13 @@ impl<'db> Type<'db> {
32493315
})
32503316
}
32513317
}
3318+
32523319
Type::Dynamic(_) => Ok(*self),
3253-
// TODO map this to a new `Type::TypeVar` variant
3254-
Type::KnownInstance(KnownInstanceType::TypeVar(_)) => Ok(*self),
3255-
Type::KnownInstance(KnownInstanceType::TypeAliasType(alias)) => {
3256-
Ok(alias.value_type(db))
3257-
}
3258-
Type::KnownInstance(KnownInstanceType::Never | KnownInstanceType::NoReturn) => {
3259-
Ok(Type::Never)
3260-
}
3261-
Type::KnownInstance(KnownInstanceType::LiteralString) => Ok(Type::LiteralString),
3262-
Type::KnownInstance(KnownInstanceType::Any) => Ok(Type::any()),
3263-
Type::KnownInstance(KnownInstanceType::Annotated) => Err(InvalidTypeExpressionError {
3264-
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::BareAnnotated],
3265-
fallback_type: Type::unknown(),
3266-
}),
3267-
Type::KnownInstance(KnownInstanceType::ClassVar) => Err(InvalidTypeExpressionError {
3268-
invalid_expressions: smallvec::smallvec![
3269-
InvalidTypeExpression::ClassVarInTypeExpression
3270-
],
3271-
fallback_type: Type::unknown(),
3272-
}),
3273-
Type::KnownInstance(KnownInstanceType::Final) => Err(InvalidTypeExpressionError {
3274-
invalid_expressions: smallvec::smallvec![
3275-
InvalidTypeExpression::FinalInTypeExpression
3276-
],
3277-
fallback_type: Type::unknown(),
3278-
}),
3279-
Type::KnownInstance(KnownInstanceType::Unknown) => Ok(Type::unknown()),
3280-
Type::KnownInstance(KnownInstanceType::AlwaysTruthy) => Ok(Type::AlwaysTruthy),
3281-
Type::KnownInstance(KnownInstanceType::AlwaysFalsy) => Ok(Type::AlwaysFalsy),
3282-
Type::KnownInstance(KnownInstanceType::Callable) => {
3283-
// TODO: Use an opt-in rule for a bare `Callable`
3284-
Ok(Type::Callable(CallableType::General(
3285-
GeneralCallableType::unknown(db),
3286-
)))
3287-
}
3288-
Type::KnownInstance(
3289-
KnownInstanceType::Literal
3290-
| KnownInstanceType::Union
3291-
| KnownInstanceType::Intersection,
3292-
) => Err(InvalidTypeExpressionError {
3293-
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::RequiresArguments(
3294-
*self
3295-
)],
3296-
fallback_type: Type::unknown(),
3297-
}),
3298-
Type::KnownInstance(
3299-
KnownInstanceType::Optional
3300-
| KnownInstanceType::Not
3301-
| KnownInstanceType::TypeOf
3302-
| KnownInstanceType::CallableTypeFromFunction,
3303-
) => Err(InvalidTypeExpressionError {
3304-
invalid_expressions: smallvec::smallvec![
3305-
InvalidTypeExpression::RequiresOneArgument(*self)
3306-
],
3307-
fallback_type: Type::unknown(),
3308-
}),
3309-
Type::KnownInstance(KnownInstanceType::Protocol) => Err(InvalidTypeExpressionError {
3310-
invalid_expressions: smallvec::smallvec![
3311-
InvalidTypeExpression::ProtocolInTypeExpression
3312-
],
3313-
fallback_type: Type::unknown(),
3314-
}),
3315-
Type::KnownInstance(
3316-
KnownInstanceType::TypingSelf
3317-
| KnownInstanceType::ReadOnly
3318-
| KnownInstanceType::TypeAlias
3319-
| KnownInstanceType::NotRequired
3320-
| KnownInstanceType::Concatenate
3321-
| KnownInstanceType::TypeIs
3322-
| KnownInstanceType::TypeGuard
3323-
| KnownInstanceType::Unpack
3324-
| KnownInstanceType::Required,
3325-
) => Ok(todo_type!(
3326-
"Invalid or unsupported `KnownInstanceType` in `Type::to_type_expression`"
3327-
)),
3320+
33283321
Type::Instance(_) => Ok(todo_type!(
33293322
"Invalid or unsupported `Instance` in `Type::to_type_expression`"
33303323
)),
3324+
33313325
Type::Intersection(_) => Ok(todo_type!("Type::Intersection.in_type_expression")),
33323326
}
33333327
}
@@ -3593,18 +3587,20 @@ impl<'db> InvalidTypeExpressionError<'db> {
35933587
/// Enumeration of various types that are invalid in type-expression contexts
35943588
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
35953589
enum InvalidTypeExpression<'db> {
3596-
/// `x: Annotated` is invalid as an annotation
3597-
BareAnnotated,
3598-
/// Some types always require at least one argument when used in a type expression
3599-
RequiresArguments(Type<'db>),
36003590
/// Some types always require exactly one argument when used in a type expression
36013591
RequiresOneArgument(Type<'db>),
3592+
/// Some types always require at least one argument when used in a type expression
3593+
RequiresArguments(Type<'db>),
3594+
/// Some types always require at least two arguments when used in a type expression
3595+
RequiresTwoArguments(Type<'db>),
36023596
/// The `Protocol` type is invalid in type expressions
3603-
ProtocolInTypeExpression,
3604-
/// The `ClassVar` type qualifier was used in a type expression
3605-
ClassVarInTypeExpression,
3606-
/// The `Final` type qualifier was used in a type expression
3607-
FinalInTypeExpression,
3597+
Protocol,
3598+
/// Type qualifiers are always invalid in *type expressions*,
3599+
/// but these ones are okay with 0 arguments in *annotation expressions*
3600+
TypeQualifier(KnownInstanceType<'db>),
3601+
/// Type qualifiers that are invalid in type expressions,
3602+
/// and which would require exactly one argument even if they appeared in an annotation expression
3603+
TypeQualifierRequiresOneArgument(KnownInstanceType<'db>),
36083604
/// Some types are always invalid in type expressions
36093605
InvalidType(Type<'db>),
36103606
}
@@ -3619,26 +3615,33 @@ impl<'db> InvalidTypeExpression<'db> {
36193615
impl std::fmt::Display for Display<'_> {
36203616
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36213617
match self.error {
3622-
InvalidTypeExpression::BareAnnotated => f.write_str(
3623-
"`Annotated` requires at least two arguments when used in an annotation or type expression"
3624-
),
36253618
InvalidTypeExpression::RequiresOneArgument(ty) => write!(
36263619
f,
36273620
"`{ty}` requires exactly one argument when used in a type expression",
3628-
ty = ty.display(self.db)),
3621+
ty = ty.display(self.db)
3622+
),
36293623
InvalidTypeExpression::RequiresArguments(ty) => write!(
36303624
f,
36313625
"`{ty}` requires at least one argument when used in a type expression",
36323626
ty = ty.display(self.db)
36333627
),
3634-
InvalidTypeExpression::ProtocolInTypeExpression => f.write_str(
3628+
InvalidTypeExpression::RequiresTwoArguments(ty) => write!(
3629+
f,
3630+
"`{ty}` requires at least two arguments when used in a type expression",
3631+
ty = ty.display(self.db)
3632+
),
3633+
InvalidTypeExpression::Protocol => f.write_str(
36353634
"`typing.Protocol` is not allowed in type expressions"
36363635
),
3637-
InvalidTypeExpression::ClassVarInTypeExpression => f.write_str(
3638-
"Type qualifier `typing.ClassVar` is not allowed in type expressions (only in annotation expressions)"
3636+
InvalidTypeExpression::TypeQualifier(qualifier) => write!(
3637+
f,
3638+
"Type qualifier `{q}` is not allowed in type expressions (only in annotation expressions)",
3639+
q = qualifier.repr(self.db)
36393640
),
3640-
InvalidTypeExpression::FinalInTypeExpression => f.write_str(
3641-
"Type qualifier `typing.Final` is not allowed in type expressions (only in annotation expressions)"
3641+
InvalidTypeExpression::TypeQualifierRequiresOneArgument(qualifier) => write!(
3642+
f,
3643+
"Type qualifier `{q}` is not allowed in type expressions (only in annotation expressions, and only with exactly one argument)",
3644+
q = qualifier.repr(self.db)
36423645
),
36433646
InvalidTypeExpression::InvalidType(ty) => write!(
36443647
f,

0 commit comments

Comments
 (0)