mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 05:15:12 +00:00
[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>
This commit is contained in:
parent
296d67a496
commit
63e78b41cd
6 changed files with 144 additions and 69 deletions
|
@ -9,6 +9,8 @@ import typing
|
||||||
from knot_extensions import AlwaysTruthy, AlwaysFalsy
|
from knot_extensions import AlwaysTruthy, AlwaysFalsy
|
||||||
from typing_extensions import Literal, Never
|
from typing_extensions import Literal, Never
|
||||||
|
|
||||||
|
class A: ...
|
||||||
|
|
||||||
def _(
|
def _(
|
||||||
a: type[int],
|
a: type[int],
|
||||||
b: AlwaysTruthy,
|
b: AlwaysTruthy,
|
||||||
|
@ -18,30 +20,34 @@ def _(
|
||||||
f: Literal[b"foo"],
|
f: Literal[b"foo"],
|
||||||
g: tuple[int, str],
|
g: tuple[int, str],
|
||||||
h: Never,
|
h: Never,
|
||||||
|
i: int,
|
||||||
|
j: A,
|
||||||
):
|
):
|
||||||
def foo(): ...
|
def foo(): ...
|
||||||
def invalid(
|
def invalid(
|
||||||
i: a, # error: [invalid-type-form] "Variable of type `type[int]` is not allowed in a type expression"
|
a_: a, # error: [invalid-type-form] "Variable of type `type[int]` is not allowed in a type expression"
|
||||||
j: b, # error: [invalid-type-form]
|
b_: b, # error: [invalid-type-form]
|
||||||
k: c, # error: [invalid-type-form]
|
c_: c, # error: [invalid-type-form]
|
||||||
l: d, # error: [invalid-type-form]
|
d_: d, # error: [invalid-type-form]
|
||||||
m: e, # error: [invalid-type-form]
|
e_: e, # error: [invalid-type-form]
|
||||||
n: f, # error: [invalid-type-form]
|
f_: f, # error: [invalid-type-form]
|
||||||
o: g, # error: [invalid-type-form]
|
g_: g, # error: [invalid-type-form]
|
||||||
p: h, # error: [invalid-type-form]
|
h_: h, # error: [invalid-type-form]
|
||||||
q: typing, # error: [invalid-type-form]
|
i_: typing, # error: [invalid-type-form]
|
||||||
r: foo, # error: [invalid-type-form]
|
j_: foo, # error: [invalid-type-form]
|
||||||
|
k_: i, # error: [invalid-type-form] "Variable of type `int` is not allowed in a type expression"
|
||||||
|
l_: j, # error: [invalid-type-form] "Variable of type `A` is not allowed in a type expression"
|
||||||
):
|
):
|
||||||
reveal_type(i) # revealed: Unknown
|
reveal_type(a_) # revealed: Unknown
|
||||||
reveal_type(j) # revealed: Unknown
|
reveal_type(b_) # revealed: Unknown
|
||||||
reveal_type(k) # revealed: Unknown
|
reveal_type(c_) # revealed: Unknown
|
||||||
reveal_type(l) # revealed: Unknown
|
reveal_type(d_) # revealed: Unknown
|
||||||
reveal_type(m) # revealed: Unknown
|
reveal_type(e_) # revealed: Unknown
|
||||||
reveal_type(n) # revealed: Unknown
|
reveal_type(f_) # revealed: Unknown
|
||||||
reveal_type(o) # revealed: Unknown
|
reveal_type(g_) # revealed: Unknown
|
||||||
reveal_type(p) # revealed: Unknown
|
reveal_type(h_) # revealed: Unknown
|
||||||
reveal_type(q) # revealed: Unknown
|
reveal_type(i_) # revealed: Unknown
|
||||||
reveal_type(r) # revealed: Unknown
|
reveal_type(j_) # revealed: Unknown
|
||||||
```
|
```
|
||||||
|
|
||||||
## Invalid AST nodes
|
## Invalid AST nodes
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
# NewType
|
||||||
|
|
||||||
|
Currently, red-knot doesn't support `typing.NewType` in type annotations.
|
||||||
|
|
||||||
|
## Valid forms
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing_extensions import NewType
|
||||||
|
from types import GenericAlias
|
||||||
|
|
||||||
|
A = NewType("A", int)
|
||||||
|
B = GenericAlias(A, ())
|
||||||
|
|
||||||
|
def _(
|
||||||
|
a: A,
|
||||||
|
b: B,
|
||||||
|
):
|
||||||
|
reveal_type(a) # revealed: @Todo(Support for `typing.NewType` instances in type expressions)
|
||||||
|
reveal_type(b) # revealed: @Todo(Support for `typing.GenericAlias` instances in type expressions)
|
||||||
|
```
|
|
@ -43,18 +43,22 @@ class Foo:
|
||||||
One thing that is supported is error messages for using special forms in type expressions.
|
One thing that is supported is error messages for using special forms in type expressions.
|
||||||
|
|
||||||
```py
|
```py
|
||||||
from typing_extensions import Unpack, TypeGuard, TypeIs, Concatenate
|
from typing_extensions import Unpack, TypeGuard, TypeIs, Concatenate, ParamSpec
|
||||||
|
|
||||||
def _(
|
def _(
|
||||||
a: Unpack, # error: [invalid-type-form] "`typing.Unpack` requires exactly one argument when used in a type expression"
|
a: Unpack, # error: [invalid-type-form] "`typing.Unpack` requires exactly one argument when used in a type expression"
|
||||||
b: TypeGuard, # error: [invalid-type-form] "`typing.TypeGuard` requires exactly one argument when used in a type expression"
|
b: TypeGuard, # error: [invalid-type-form] "`typing.TypeGuard` requires exactly one argument when used in a type expression"
|
||||||
c: TypeIs, # error: [invalid-type-form] "`typing.TypeIs` requires exactly one argument when used in a type expression"
|
c: TypeIs, # error: [invalid-type-form] "`typing.TypeIs` requires exactly one argument when used in a type expression"
|
||||||
d: Concatenate, # error: [invalid-type-form] "`typing.Concatenate` requires at least two arguments when used in a type expression"
|
d: Concatenate, # error: [invalid-type-form] "`typing.Concatenate` requires at least two arguments when used in a type expression"
|
||||||
|
e: ParamSpec,
|
||||||
) -> None:
|
) -> None:
|
||||||
reveal_type(a) # revealed: Unknown
|
reveal_type(a) # revealed: Unknown
|
||||||
reveal_type(b) # revealed: Unknown
|
reveal_type(b) # revealed: Unknown
|
||||||
reveal_type(c) # revealed: Unknown
|
reveal_type(c) # revealed: Unknown
|
||||||
reveal_type(d) # revealed: Unknown
|
reveal_type(d) # revealed: Unknown
|
||||||
|
|
||||||
|
def foo(a_: e) -> None:
|
||||||
|
reveal_type(a_) # revealed: @Todo(Support for `typing.ParamSpec` instances in type expressions)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Inheritance
|
## Inheritance
|
||||||
|
|
|
@ -113,7 +113,7 @@ class Legacy(Generic[T]):
|
||||||
|
|
||||||
legacy: Legacy[int] = Legacy()
|
legacy: Legacy[int] = Legacy()
|
||||||
# TODO: revealed: str
|
# TODO: revealed: str
|
||||||
reveal_type(legacy.m(1, "string")) # revealed: @Todo(Invalid or unsupported `Instance` in `Type::to_type_expression`)
|
reveal_type(legacy.m(1, "string")) # revealed: @Todo(Support for `typing.TypeVar` instances in type expressions)
|
||||||
```
|
```
|
||||||
|
|
||||||
With PEP 695 syntax, it is clearer that the method uses a separate typevar:
|
With PEP 695 syntax, it is clearer that the method uses a separate typevar:
|
||||||
|
|
|
@ -3318,9 +3318,29 @@ impl<'db> Type<'db> {
|
||||||
|
|
||||||
Type::Dynamic(_) => Ok(*self),
|
Type::Dynamic(_) => Ok(*self),
|
||||||
|
|
||||||
Type::Instance(_) => Ok(todo_type!(
|
Type::Instance(InstanceType { class }) => match class.known(db) {
|
||||||
"Invalid or unsupported `Instance` in `Type::to_type_expression`"
|
Some(KnownClass::TypeVar) => Ok(todo_type!(
|
||||||
|
"Support for `typing.TypeVar` instances in type expressions"
|
||||||
)),
|
)),
|
||||||
|
Some(KnownClass::ParamSpec) => Ok(todo_type!(
|
||||||
|
"Support for `typing.ParamSpec` instances in type expressions"
|
||||||
|
)),
|
||||||
|
Some(KnownClass::TypeVarTuple) => Ok(todo_type!(
|
||||||
|
"Support for `typing.TypeVarTuple` instances in type expressions"
|
||||||
|
)),
|
||||||
|
Some(KnownClass::NewType) => Ok(todo_type!(
|
||||||
|
"Support for `typing.NewType` instances in type expressions"
|
||||||
|
)),
|
||||||
|
Some(KnownClass::GenericAlias) => Ok(todo_type!(
|
||||||
|
"Support for `typing.GenericAlias` instances in type expressions"
|
||||||
|
)),
|
||||||
|
_ => Err(InvalidTypeExpressionError {
|
||||||
|
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::InvalidType(
|
||||||
|
*self
|
||||||
|
)],
|
||||||
|
fallback_type: Type::unknown(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
Type::Intersection(_) => Ok(todo_type!("Type::Intersection.in_type_expression")),
|
Type::Intersection(_) => Ok(todo_type!("Type::Intersection.in_type_expression")),
|
||||||
}
|
}
|
||||||
|
|
|
@ -829,8 +829,11 @@ pub enum KnownClass {
|
||||||
StdlibAlias,
|
StdlibAlias,
|
||||||
SpecialForm,
|
SpecialForm,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
|
ParamSpec,
|
||||||
|
TypeVarTuple,
|
||||||
TypeAliasType,
|
TypeAliasType,
|
||||||
NoDefaultType,
|
NoDefaultType,
|
||||||
|
NewType,
|
||||||
// TODO: This can probably be removed when we have support for protocols
|
// TODO: This can probably be removed when we have support for protocols
|
||||||
SupportsIndex,
|
SupportsIndex,
|
||||||
// Collections
|
// Collections
|
||||||
|
@ -873,6 +876,8 @@ impl<'db> KnownClass {
|
||||||
| Self::VersionInfo
|
| Self::VersionInfo
|
||||||
| Self::TypeAliasType
|
| Self::TypeAliasType
|
||||||
| Self::TypeVar
|
| Self::TypeVar
|
||||||
|
| Self::ParamSpec
|
||||||
|
| Self::TypeVarTuple
|
||||||
| Self::WrapperDescriptorType
|
| Self::WrapperDescriptorType
|
||||||
| Self::MethodWrapperType => Truthiness::AlwaysTrue,
|
| Self::MethodWrapperType => Truthiness::AlwaysTrue,
|
||||||
|
|
||||||
|
@ -886,6 +891,7 @@ impl<'db> KnownClass {
|
||||||
| Self::Str
|
| Self::Str
|
||||||
| Self::List
|
| Self::List
|
||||||
| Self::GenericAlias
|
| Self::GenericAlias
|
||||||
|
| Self::NewType
|
||||||
| Self::StdlibAlias
|
| Self::StdlibAlias
|
||||||
| Self::SupportsIndex
|
| Self::SupportsIndex
|
||||||
| Self::Set
|
| Self::Set
|
||||||
|
@ -939,8 +945,11 @@ impl<'db> KnownClass {
|
||||||
Self::NoneType => "NoneType",
|
Self::NoneType => "NoneType",
|
||||||
Self::SpecialForm => "_SpecialForm",
|
Self::SpecialForm => "_SpecialForm",
|
||||||
Self::TypeVar => "TypeVar",
|
Self::TypeVar => "TypeVar",
|
||||||
|
Self::ParamSpec => "ParamSpec",
|
||||||
|
Self::TypeVarTuple => "TypeVarTuple",
|
||||||
Self::TypeAliasType => "TypeAliasType",
|
Self::TypeAliasType => "TypeAliasType",
|
||||||
Self::NoDefaultType => "_NoDefaultType",
|
Self::NoDefaultType => "_NoDefaultType",
|
||||||
|
Self::NewType => "NewType",
|
||||||
Self::SupportsIndex => "SupportsIndex",
|
Self::SupportsIndex => "SupportsIndex",
|
||||||
Self::ChainMap => "ChainMap",
|
Self::ChainMap => "ChainMap",
|
||||||
Self::Counter => "Counter",
|
Self::Counter => "Counter",
|
||||||
|
@ -1109,7 +1118,9 @@ impl<'db> KnownClass {
|
||||||
Self::SpecialForm | Self::TypeVar | Self::StdlibAlias | Self::SupportsIndex => {
|
Self::SpecialForm | Self::TypeVar | Self::StdlibAlias | Self::SupportsIndex => {
|
||||||
KnownModule::Typing
|
KnownModule::Typing
|
||||||
}
|
}
|
||||||
Self::TypeAliasType => KnownModule::TypingExtensions,
|
Self::TypeAliasType | Self::TypeVarTuple | Self::ParamSpec | Self::NewType => {
|
||||||
|
KnownModule::TypingExtensions
|
||||||
|
}
|
||||||
Self::NoDefaultType => {
|
Self::NoDefaultType => {
|
||||||
let python_version = Program::get(db).python_version(db);
|
let python_version = Program::get(db).python_version(db);
|
||||||
|
|
||||||
|
@ -1142,46 +1153,49 @@ impl<'db> KnownClass {
|
||||||
/// Return true if all instances of this `KnownClass` compare equal.
|
/// Return true if all instances of this `KnownClass` compare equal.
|
||||||
pub(super) const fn is_single_valued(self) -> bool {
|
pub(super) const fn is_single_valued(self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
KnownClass::NoneType
|
Self::NoneType
|
||||||
| KnownClass::NoDefaultType
|
| Self::NoDefaultType
|
||||||
| KnownClass::VersionInfo
|
| Self::VersionInfo
|
||||||
| KnownClass::EllipsisType
|
| Self::EllipsisType
|
||||||
| KnownClass::TypeAliasType => true,
|
| Self::TypeAliasType => true,
|
||||||
|
|
||||||
KnownClass::Bool
|
Self::Bool
|
||||||
| KnownClass::Object
|
| Self::Object
|
||||||
| KnownClass::Bytes
|
| Self::Bytes
|
||||||
| KnownClass::Type
|
| Self::Type
|
||||||
| KnownClass::Int
|
| Self::Int
|
||||||
| KnownClass::Float
|
| Self::Float
|
||||||
| KnownClass::Complex
|
| Self::Complex
|
||||||
| KnownClass::Str
|
| Self::Str
|
||||||
| KnownClass::List
|
| Self::List
|
||||||
| KnownClass::Tuple
|
| Self::Tuple
|
||||||
| KnownClass::Set
|
| Self::Set
|
||||||
| KnownClass::FrozenSet
|
| Self::FrozenSet
|
||||||
| KnownClass::Dict
|
| Self::Dict
|
||||||
| KnownClass::Slice
|
| Self::Slice
|
||||||
| KnownClass::Range
|
| Self::Range
|
||||||
| KnownClass::Property
|
| Self::Property
|
||||||
| KnownClass::BaseException
|
| Self::BaseException
|
||||||
| KnownClass::BaseExceptionGroup
|
| Self::BaseExceptionGroup
|
||||||
| KnownClass::Classmethod
|
| Self::Classmethod
|
||||||
| KnownClass::GenericAlias
|
| Self::GenericAlias
|
||||||
| KnownClass::ModuleType
|
| Self::ModuleType
|
||||||
| KnownClass::FunctionType
|
| Self::FunctionType
|
||||||
| KnownClass::MethodType
|
| Self::MethodType
|
||||||
| KnownClass::MethodWrapperType
|
| Self::MethodWrapperType
|
||||||
| KnownClass::WrapperDescriptorType
|
| Self::WrapperDescriptorType
|
||||||
| KnownClass::SpecialForm
|
| Self::SpecialForm
|
||||||
| KnownClass::ChainMap
|
| Self::ChainMap
|
||||||
| KnownClass::Counter
|
| Self::Counter
|
||||||
| KnownClass::DefaultDict
|
| Self::DefaultDict
|
||||||
| KnownClass::Deque
|
| Self::Deque
|
||||||
| KnownClass::OrderedDict
|
| Self::OrderedDict
|
||||||
| KnownClass::SupportsIndex
|
| Self::SupportsIndex
|
||||||
| KnownClass::StdlibAlias
|
| Self::StdlibAlias
|
||||||
| KnownClass::TypeVar => false,
|
| Self::TypeVar
|
||||||
|
| Self::ParamSpec
|
||||||
|
| Self::TypeVarTuple
|
||||||
|
| Self::NewType => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1230,7 +1244,10 @@ impl<'db> KnownClass {
|
||||||
| Self::BaseException
|
| Self::BaseException
|
||||||
| Self::BaseExceptionGroup
|
| Self::BaseExceptionGroup
|
||||||
| Self::Classmethod
|
| Self::Classmethod
|
||||||
| Self::TypeVar => false,
|
| Self::TypeVar
|
||||||
|
| Self::ParamSpec
|
||||||
|
| Self::TypeVarTuple
|
||||||
|
| Self::NewType => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1268,8 +1285,11 @@ impl<'db> KnownClass {
|
||||||
"MethodType" => Self::MethodType,
|
"MethodType" => Self::MethodType,
|
||||||
"MethodWrapperType" => Self::MethodWrapperType,
|
"MethodWrapperType" => Self::MethodWrapperType,
|
||||||
"WrapperDescriptorType" => Self::WrapperDescriptorType,
|
"WrapperDescriptorType" => Self::WrapperDescriptorType,
|
||||||
|
"NewType" => Self::NewType,
|
||||||
"TypeAliasType" => Self::TypeAliasType,
|
"TypeAliasType" => Self::TypeAliasType,
|
||||||
"TypeVar" => Self::TypeVar,
|
"TypeVar" => Self::TypeVar,
|
||||||
|
"ParamSpec" => Self::ParamSpec,
|
||||||
|
"TypeVarTuple" => Self::TypeVarTuple,
|
||||||
"ChainMap" => Self::ChainMap,
|
"ChainMap" => Self::ChainMap,
|
||||||
"Counter" => Self::Counter,
|
"Counter" => Self::Counter,
|
||||||
"defaultdict" => Self::DefaultDict,
|
"defaultdict" => Self::DefaultDict,
|
||||||
|
@ -1331,9 +1351,14 @@ impl<'db> KnownClass {
|
||||||
| Self::MethodWrapperType
|
| Self::MethodWrapperType
|
||||||
| Self::WrapperDescriptorType => module == self.canonical_module(db),
|
| Self::WrapperDescriptorType => module == self.canonical_module(db),
|
||||||
Self::NoneType => matches!(module, KnownModule::Typeshed | KnownModule::Types),
|
Self::NoneType => matches!(module, KnownModule::Typeshed | KnownModule::Types),
|
||||||
Self::SpecialForm | Self::TypeVar | Self::TypeAliasType | Self::NoDefaultType | Self::SupportsIndex => {
|
Self::SpecialForm
|
||||||
matches!(module, KnownModule::Typing | KnownModule::TypingExtensions)
|
| Self::TypeVar
|
||||||
}
|
| Self::TypeAliasType
|
||||||
|
| Self::NoDefaultType
|
||||||
|
| Self::SupportsIndex
|
||||||
|
| Self::ParamSpec
|
||||||
|
| Self::TypeVarTuple
|
||||||
|
| Self::NewType => matches!(module, KnownModule::Typing | KnownModule::TypingExtensions),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue