Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import typing
from knot_extensions import AlwaysTruthy, AlwaysFalsy
from typing_extensions import Literal, Never

class A: ...

def _(
a: type[int],
b: AlwaysTruthy,
Expand All @@ -18,30 +20,34 @@ def _(
f: Literal[b"foo"],
g: tuple[int, str],
h: Never,
i: int,
j: A,
):
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]
a_: a, # error: [invalid-type-form] "Variable of type `type[int]` is not allowed in a type expression"
b_: b, # error: [invalid-type-form]
c_: c, # error: [invalid-type-form]
d_: d, # error: [invalid-type-form]
e_: e, # error: [invalid-type-form]
f_: f, # error: [invalid-type-form]
g_: g, # error: [invalid-type-form]
h_: h, # error: [invalid-type-form]
i_: typing, # 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(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
reveal_type(a_) # revealed: Unknown
reveal_type(b_) # revealed: Unknown
reveal_type(c_) # revealed: Unknown
reveal_type(d_) # revealed: Unknown
reveal_type(e_) # revealed: Unknown
reveal_type(f_) # revealed: Unknown
reveal_type(g_) # revealed: Unknown
reveal_type(h_) # revealed: Unknown
reveal_type(i_) # revealed: Unknown
reveal_type(j_) # revealed: Unknown
```

## Invalid AST nodes
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
```
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,22 @@ class Foo:
One thing that is supported is error messages for using special forms in type expressions.

```py
from typing_extensions import Unpack, TypeGuard, TypeIs, Concatenate
from typing_extensions import Unpack, TypeGuard, TypeIs, Concatenate, ParamSpec

def _(
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"
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"
e: ParamSpec,
) -> None:
reveal_type(a) # revealed: Unknown
reveal_type(b) # revealed: Unknown
reveal_type(c) # 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ class Legacy(Generic[T]):

legacy: Legacy[int] = Legacy()
# 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:
Expand Down
26 changes: 23 additions & 3 deletions crates/red_knot_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3318,9 +3318,29 @@ impl<'db> Type<'db> {

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

Type::Instance(_) => Ok(todo_type!(
"Invalid or unsupported `Instance` in `Type::to_type_expression`"
)),
Type::Instance(InstanceType { class }) => match class.known(db) {
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")),
}
Expand Down
115 changes: 70 additions & 45 deletions crates/red_knot_python_semantic/src/types/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -829,8 +829,11 @@ pub enum KnownClass {
StdlibAlias,
SpecialForm,
TypeVar,
ParamSpec,
TypeVarTuple,
TypeAliasType,
NoDefaultType,
NewType,
// TODO: This can probably be removed when we have support for protocols
SupportsIndex,
// Collections
Expand Down Expand Up @@ -869,6 +872,8 @@ impl<'db> KnownClass {
| Self::VersionInfo
| Self::TypeAliasType
| Self::TypeVar
| Self::ParamSpec
| Self::TypeVarTuple
| Self::WrapperDescriptorType
| Self::MethodWrapperType => Truthiness::AlwaysTrue,

Expand All @@ -882,6 +887,7 @@ impl<'db> KnownClass {
| Self::Str
| Self::List
| Self::GenericAlias
| Self::NewType
| Self::StdlibAlias
| Self::SupportsIndex
| Self::Set
Expand Down Expand Up @@ -935,8 +941,11 @@ impl<'db> KnownClass {
Self::NoneType => "NoneType",
Self::SpecialForm => "_SpecialForm",
Self::TypeVar => "TypeVar",
Self::ParamSpec => "ParamSpec",
Self::TypeVarTuple => "TypeVarTuple",
Self::TypeAliasType => "TypeAliasType",
Self::NoDefaultType => "_NoDefaultType",
Self::NewType => "NewType",
Self::SupportsIndex => "SupportsIndex",
Self::ChainMap => "ChainMap",
Self::Counter => "Counter",
Expand Down Expand Up @@ -1105,7 +1114,9 @@ impl<'db> KnownClass {
Self::SpecialForm | Self::TypeVar | Self::StdlibAlias | Self::SupportsIndex => {
KnownModule::Typing
}
Self::TypeAliasType => KnownModule::TypingExtensions,
Self::TypeAliasType | Self::TypeVarTuple | Self::ParamSpec | Self::NewType => {
KnownModule::TypingExtensions
}
Self::NoDefaultType => {
let python_version = Program::get(db).python_version(db);

Expand Down Expand Up @@ -1138,46 +1149,49 @@ impl<'db> KnownClass {
/// Return true if all instances of this `KnownClass` compare equal.
pub(super) const fn is_single_valued(self) -> bool {
match self {
KnownClass::NoneType
| KnownClass::NoDefaultType
| KnownClass::VersionInfo
| KnownClass::EllipsisType
| KnownClass::TypeAliasType => true,

KnownClass::Bool
| KnownClass::Object
| KnownClass::Bytes
| KnownClass::Type
| KnownClass::Int
| KnownClass::Float
| KnownClass::Complex
| KnownClass::Str
| KnownClass::List
| KnownClass::Tuple
| KnownClass::Set
| KnownClass::FrozenSet
| KnownClass::Dict
| KnownClass::Slice
| KnownClass::Range
| KnownClass::Property
| KnownClass::BaseException
| KnownClass::BaseExceptionGroup
| KnownClass::Classmethod
| KnownClass::GenericAlias
| KnownClass::ModuleType
| KnownClass::FunctionType
| KnownClass::MethodType
| KnownClass::MethodWrapperType
| KnownClass::WrapperDescriptorType
| KnownClass::SpecialForm
| KnownClass::ChainMap
| KnownClass::Counter
| KnownClass::DefaultDict
| KnownClass::Deque
| KnownClass::OrderedDict
| KnownClass::SupportsIndex
| KnownClass::StdlibAlias
| KnownClass::TypeVar => false,
Self::NoneType
| Self::NoDefaultType
| Self::VersionInfo
| Self::EllipsisType
| Self::TypeAliasType => true,

Self::Bool
| Self::Object
| Self::Bytes
| Self::Type
| Self::Int
| Self::Float
| Self::Complex
| Self::Str
| Self::List
| Self::Tuple
| Self::Set
| Self::FrozenSet
| Self::Dict
| Self::Slice
| Self::Range
| Self::Property
| Self::BaseException
| Self::BaseExceptionGroup
| Self::Classmethod
| Self::GenericAlias
| Self::ModuleType
| Self::FunctionType
| Self::MethodType
| Self::MethodWrapperType
| Self::WrapperDescriptorType
| Self::SpecialForm
| Self::ChainMap
| Self::Counter
| Self::DefaultDict
| Self::Deque
| Self::OrderedDict
| Self::SupportsIndex
| Self::StdlibAlias
| Self::TypeVar
| Self::ParamSpec
| Self::TypeVarTuple
| Self::NewType => false,
}
}

Expand Down Expand Up @@ -1226,7 +1240,10 @@ impl<'db> KnownClass {
| Self::BaseException
| Self::BaseExceptionGroup
| Self::Classmethod
| Self::TypeVar => false,
| Self::TypeVar
| Self::ParamSpec
| Self::TypeVarTuple
| Self::NewType => false,
}
}

Expand Down Expand Up @@ -1264,8 +1281,11 @@ impl<'db> KnownClass {
"MethodType" => Self::MethodType,
"MethodWrapperType" => Self::MethodWrapperType,
"WrapperDescriptorType" => Self::WrapperDescriptorType,
"NewType" => Self::NewType,
"TypeAliasType" => Self::TypeAliasType,
"TypeVar" => Self::TypeVar,
"ParamSpec" => Self::ParamSpec,
"TypeVarTuple" => Self::TypeVarTuple,
"ChainMap" => Self::ChainMap,
"Counter" => Self::Counter,
"defaultdict" => Self::DefaultDict,
Expand Down Expand Up @@ -1327,9 +1347,14 @@ impl<'db> KnownClass {
| Self::MethodWrapperType
| Self::WrapperDescriptorType => module == self.canonical_module(db),
Self::NoneType => matches!(module, KnownModule::Typeshed | KnownModule::Types),
Self::SpecialForm | Self::TypeVar | Self::TypeAliasType | Self::NoDefaultType | Self::SupportsIndex => {
matches!(module, KnownModule::Typing | KnownModule::TypingExtensions)
}
Self::SpecialForm
| Self::TypeVar
| Self::TypeAliasType
| Self::NoDefaultType
| Self::SupportsIndex
| Self::ParamSpec
| Self::TypeVarTuple
| Self::NewType => matches!(module, KnownModule::Typing | KnownModule::TypingExtensions),
}
}
}
Expand Down
Loading