Skip to content

Commit 63e78b4

Browse files
[red-knot] Ban most Type::Instance types in type expressions (#16872)
## Summary Catch some Instances, but raise type error for the rest of them Fixes #16851 ## Test Plan Extend invalid.md in annotations --------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
1 parent 296d67a commit 63e78b4

File tree

6 files changed

+145
-70
lines changed

6 files changed

+145
-70
lines changed

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

+26-20
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import typing
99
from knot_extensions import AlwaysTruthy, AlwaysFalsy
1010
from typing_extensions import Literal, Never
1111

12+
class A: ...
13+
1214
def _(
1315
a: type[int],
1416
b: AlwaysTruthy,
@@ -18,30 +20,34 @@ def _(
1820
f: Literal[b"foo"],
1921
g: tuple[int, str],
2022
h: Never,
23+
i: int,
24+
j: A,
2125
):
2226
def foo(): ...
2327
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]
28+
a_: a, # error: [invalid-type-form] "Variable of type `type[int]` is not allowed in a type expression"
29+
b_: b, # error: [invalid-type-form]
30+
c_: c, # error: [invalid-type-form]
31+
d_: d, # error: [invalid-type-form]
32+
e_: e, # error: [invalid-type-form]
33+
f_: f, # error: [invalid-type-form]
34+
g_: g, # error: [invalid-type-form]
35+
h_: h, # error: [invalid-type-form]
36+
i_: typing, # error: [invalid-type-form]
37+
j_: foo, # error: [invalid-type-form]
38+
k_: i, # error: [invalid-type-form] "Variable of type `int` is not allowed in a type expression"
39+
l_: j, # error: [invalid-type-form] "Variable of type `A` is not allowed in a type expression"
3440
):
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
41+
reveal_type(a_) # revealed: Unknown
42+
reveal_type(b_) # revealed: Unknown
43+
reveal_type(c_) # revealed: Unknown
44+
reveal_type(d_) # revealed: Unknown
45+
reveal_type(e_) # revealed: Unknown
46+
reveal_type(f_) # revealed: Unknown
47+
reveal_type(g_) # revealed: Unknown
48+
reveal_type(h_) # revealed: Unknown
49+
reveal_type(i_) # revealed: Unknown
50+
reveal_type(j_) # revealed: Unknown
4551
```
4652

4753
## Invalid AST nodes
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# NewType
2+
3+
Currently, red-knot doesn't support `typing.NewType` in type annotations.
4+
5+
## Valid forms
6+
7+
```py
8+
from typing_extensions import NewType
9+
from types import GenericAlias
10+
11+
A = NewType("A", int)
12+
B = GenericAlias(A, ())
13+
14+
def _(
15+
a: A,
16+
b: B,
17+
):
18+
reveal_type(a) # revealed: @Todo(Support for `typing.NewType` instances in type expressions)
19+
reveal_type(b) # revealed: @Todo(Support for `typing.GenericAlias` instances in type expressions)
20+
```

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

+5-1
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,22 @@ class Foo:
4343
One thing that is supported is error messages for using special forms in type expressions.
4444

4545
```py
46-
from typing_extensions import Unpack, TypeGuard, TypeIs, Concatenate
46+
from typing_extensions import Unpack, TypeGuard, TypeIs, Concatenate, ParamSpec
4747

4848
def _(
4949
a: Unpack, # error: [invalid-type-form] "`typing.Unpack` requires exactly one argument when used in a type expression"
5050
b: TypeGuard, # error: [invalid-type-form] "`typing.TypeGuard` requires exactly one argument when used in a type expression"
5151
c: TypeIs, # error: [invalid-type-form] "`typing.TypeIs` requires exactly one argument when used in a type expression"
5252
d: Concatenate, # error: [invalid-type-form] "`typing.Concatenate` requires at least two arguments when used in a type expression"
53+
e: ParamSpec,
5354
) -> None:
5455
reveal_type(a) # revealed: Unknown
5556
reveal_type(b) # revealed: Unknown
5657
reveal_type(c) # revealed: Unknown
5758
reveal_type(d) # revealed: Unknown
59+
60+
def foo(a_: e) -> None:
61+
reveal_type(a_) # revealed: @Todo(Support for `typing.ParamSpec` instances in type expressions)
5862
```
5963

6064
## Inheritance

crates/red_knot_python_semantic/resources/mdtest/generics/scoping.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ class Legacy(Generic[T]):
113113

114114
legacy: Legacy[int] = Legacy()
115115
# TODO: revealed: str
116-
reveal_type(legacy.m(1, "string")) # revealed: @Todo(Invalid or unsupported `Instance` in `Type::to_type_expression`)
116+
reveal_type(legacy.m(1, "string")) # revealed: @Todo(Support for `typing.TypeVar` instances in type expressions)
117117
```
118118

119119
With PEP 695 syntax, it is clearer that the method uses a separate typevar:

crates/red_knot_python_semantic/src/types.rs

+23-3
Original file line numberDiff line numberDiff line change
@@ -3318,9 +3318,29 @@ impl<'db> Type<'db> {
33183318

33193319
Type::Dynamic(_) => Ok(*self),
33203320

3321-
Type::Instance(_) => Ok(todo_type!(
3322-
"Invalid or unsupported `Instance` in `Type::to_type_expression`"
3323-
)),
3321+
Type::Instance(InstanceType { class }) => match class.known(db) {
3322+
Some(KnownClass::TypeVar) => Ok(todo_type!(
3323+
"Support for `typing.TypeVar` instances in type expressions"
3324+
)),
3325+
Some(KnownClass::ParamSpec) => Ok(todo_type!(
3326+
"Support for `typing.ParamSpec` instances in type expressions"
3327+
)),
3328+
Some(KnownClass::TypeVarTuple) => Ok(todo_type!(
3329+
"Support for `typing.TypeVarTuple` instances in type expressions"
3330+
)),
3331+
Some(KnownClass::NewType) => Ok(todo_type!(
3332+
"Support for `typing.NewType` instances in type expressions"
3333+
)),
3334+
Some(KnownClass::GenericAlias) => Ok(todo_type!(
3335+
"Support for `typing.GenericAlias` instances in type expressions"
3336+
)),
3337+
_ => Err(InvalidTypeExpressionError {
3338+
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::InvalidType(
3339+
*self
3340+
)],
3341+
fallback_type: Type::unknown(),
3342+
}),
3343+
},
33243344

33253345
Type::Intersection(_) => Ok(todo_type!("Type::Intersection.in_type_expression")),
33263346
}

crates/red_knot_python_semantic/src/types/class.rs

+70-45
Original file line numberDiff line numberDiff line change
@@ -829,8 +829,11 @@ pub enum KnownClass {
829829
StdlibAlias,
830830
SpecialForm,
831831
TypeVar,
832+
ParamSpec,
833+
TypeVarTuple,
832834
TypeAliasType,
833835
NoDefaultType,
836+
NewType,
834837
// TODO: This can probably be removed when we have support for protocols
835838
SupportsIndex,
836839
// Collections
@@ -873,6 +876,8 @@ impl<'db> KnownClass {
873876
| Self::VersionInfo
874877
| Self::TypeAliasType
875878
| Self::TypeVar
879+
| Self::ParamSpec
880+
| Self::TypeVarTuple
876881
| Self::WrapperDescriptorType
877882
| Self::MethodWrapperType => Truthiness::AlwaysTrue,
878883

@@ -886,6 +891,7 @@ impl<'db> KnownClass {
886891
| Self::Str
887892
| Self::List
888893
| Self::GenericAlias
894+
| Self::NewType
889895
| Self::StdlibAlias
890896
| Self::SupportsIndex
891897
| Self::Set
@@ -939,8 +945,11 @@ impl<'db> KnownClass {
939945
Self::NoneType => "NoneType",
940946
Self::SpecialForm => "_SpecialForm",
941947
Self::TypeVar => "TypeVar",
948+
Self::ParamSpec => "ParamSpec",
949+
Self::TypeVarTuple => "TypeVarTuple",
942950
Self::TypeAliasType => "TypeAliasType",
943951
Self::NoDefaultType => "_NoDefaultType",
952+
Self::NewType => "NewType",
944953
Self::SupportsIndex => "SupportsIndex",
945954
Self::ChainMap => "ChainMap",
946955
Self::Counter => "Counter",
@@ -1109,7 +1118,9 @@ impl<'db> KnownClass {
11091118
Self::SpecialForm | Self::TypeVar | Self::StdlibAlias | Self::SupportsIndex => {
11101119
KnownModule::Typing
11111120
}
1112-
Self::TypeAliasType => KnownModule::TypingExtensions,
1121+
Self::TypeAliasType | Self::TypeVarTuple | Self::ParamSpec | Self::NewType => {
1122+
KnownModule::TypingExtensions
1123+
}
11131124
Self::NoDefaultType => {
11141125
let python_version = Program::get(db).python_version(db);
11151126

@@ -1142,46 +1153,49 @@ impl<'db> KnownClass {
11421153
/// Return true if all instances of this `KnownClass` compare equal.
11431154
pub(super) const fn is_single_valued(self) -> bool {
11441155
match self {
1145-
KnownClass::NoneType
1146-
| KnownClass::NoDefaultType
1147-
| KnownClass::VersionInfo
1148-
| KnownClass::EllipsisType
1149-
| KnownClass::TypeAliasType => true,
1150-
1151-
KnownClass::Bool
1152-
| KnownClass::Object
1153-
| KnownClass::Bytes
1154-
| KnownClass::Type
1155-
| KnownClass::Int
1156-
| KnownClass::Float
1157-
| KnownClass::Complex
1158-
| KnownClass::Str
1159-
| KnownClass::List
1160-
| KnownClass::Tuple
1161-
| KnownClass::Set
1162-
| KnownClass::FrozenSet
1163-
| KnownClass::Dict
1164-
| KnownClass::Slice
1165-
| KnownClass::Range
1166-
| KnownClass::Property
1167-
| KnownClass::BaseException
1168-
| KnownClass::BaseExceptionGroup
1169-
| KnownClass::Classmethod
1170-
| KnownClass::GenericAlias
1171-
| KnownClass::ModuleType
1172-
| KnownClass::FunctionType
1173-
| KnownClass::MethodType
1174-
| KnownClass::MethodWrapperType
1175-
| KnownClass::WrapperDescriptorType
1176-
| KnownClass::SpecialForm
1177-
| KnownClass::ChainMap
1178-
| KnownClass::Counter
1179-
| KnownClass::DefaultDict
1180-
| KnownClass::Deque
1181-
| KnownClass::OrderedDict
1182-
| KnownClass::SupportsIndex
1183-
| KnownClass::StdlibAlias
1184-
| KnownClass::TypeVar => false,
1156+
Self::NoneType
1157+
| Self::NoDefaultType
1158+
| Self::VersionInfo
1159+
| Self::EllipsisType
1160+
| Self::TypeAliasType => true,
1161+
1162+
Self::Bool
1163+
| Self::Object
1164+
| Self::Bytes
1165+
| Self::Type
1166+
| Self::Int
1167+
| Self::Float
1168+
| Self::Complex
1169+
| Self::Str
1170+
| Self::List
1171+
| Self::Tuple
1172+
| Self::Set
1173+
| Self::FrozenSet
1174+
| Self::Dict
1175+
| Self::Slice
1176+
| Self::Range
1177+
| Self::Property
1178+
| Self::BaseException
1179+
| Self::BaseExceptionGroup
1180+
| Self::Classmethod
1181+
| Self::GenericAlias
1182+
| Self::ModuleType
1183+
| Self::FunctionType
1184+
| Self::MethodType
1185+
| Self::MethodWrapperType
1186+
| Self::WrapperDescriptorType
1187+
| Self::SpecialForm
1188+
| Self::ChainMap
1189+
| Self::Counter
1190+
| Self::DefaultDict
1191+
| Self::Deque
1192+
| Self::OrderedDict
1193+
| Self::SupportsIndex
1194+
| Self::StdlibAlias
1195+
| Self::TypeVar
1196+
| Self::ParamSpec
1197+
| Self::TypeVarTuple
1198+
| Self::NewType => false,
11851199
}
11861200
}
11871201

@@ -1230,7 +1244,10 @@ impl<'db> KnownClass {
12301244
| Self::BaseException
12311245
| Self::BaseExceptionGroup
12321246
| Self::Classmethod
1233-
| Self::TypeVar => false,
1247+
| Self::TypeVar
1248+
| Self::ParamSpec
1249+
| Self::TypeVarTuple
1250+
| Self::NewType => false,
12341251
}
12351252
}
12361253

@@ -1268,8 +1285,11 @@ impl<'db> KnownClass {
12681285
"MethodType" => Self::MethodType,
12691286
"MethodWrapperType" => Self::MethodWrapperType,
12701287
"WrapperDescriptorType" => Self::WrapperDescriptorType,
1288+
"NewType" => Self::NewType,
12711289
"TypeAliasType" => Self::TypeAliasType,
12721290
"TypeVar" => Self::TypeVar,
1291+
"ParamSpec" => Self::ParamSpec,
1292+
"TypeVarTuple" => Self::TypeVarTuple,
12731293
"ChainMap" => Self::ChainMap,
12741294
"Counter" => Self::Counter,
12751295
"defaultdict" => Self::DefaultDict,
@@ -1331,9 +1351,14 @@ impl<'db> KnownClass {
13311351
| Self::MethodWrapperType
13321352
| Self::WrapperDescriptorType => module == self.canonical_module(db),
13331353
Self::NoneType => matches!(module, KnownModule::Typeshed | KnownModule::Types),
1334-
Self::SpecialForm | Self::TypeVar | Self::TypeAliasType | Self::NoDefaultType | Self::SupportsIndex => {
1335-
matches!(module, KnownModule::Typing | KnownModule::TypingExtensions)
1336-
}
1354+
Self::SpecialForm
1355+
| Self::TypeVar
1356+
| Self::TypeAliasType
1357+
| Self::NoDefaultType
1358+
| Self::SupportsIndex
1359+
| Self::ParamSpec
1360+
| Self::TypeVarTuple
1361+
| Self::NewType => matches!(module, KnownModule::Typing | KnownModule::TypingExtensions),
13371362
}
13381363
}
13391364
}

0 commit comments

Comments
 (0)