diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/stdlib_typing_aliases.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/stdlib_typing_aliases.md index 452fa9f015e24..057efca54cce4 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/annotations/stdlib_typing_aliases.md +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/stdlib_typing_aliases.md @@ -3,43 +3,59 @@ The `typing` module has various aliases to other stdlib classes. These are a legacy feature, but still need to be supported by a type checker. -## Currently unsupported +## Correspondence -Support for most of these symbols is currently a TODO: +All of the following symbols can be mapped one-to-one with the actual type: ```py import typing def f( - a: typing.List, - b: typing.List[int], - c: typing.Dict, - d: typing.Dict[int, str], - e: typing.DefaultDict, - f: typing.DefaultDict[str, int], - g: typing.Set, - h: typing.Set[int], - i: typing.FrozenSet, - j: typing.FrozenSet[str], - k: typing.OrderedDict, - l: typing.OrderedDict[int, str], - m: typing.Counter, - n: typing.Counter[int], + list_bare: typing.List, + list_parametrized: typing.List[int], + dict_bare: typing.Dict, + dict_parametrized: typing.Dict[int, str], + set_bare: typing.Set, + set_parametrized: typing.Set[int], + frozen_set_bare: typing.FrozenSet, + frozen_set_parametrized: typing.FrozenSet[str], + chain_map_bare: typing.ChainMap, + chain_map_parametrized: typing.ChainMap[int], + counter_bare: typing.Counter, + counter_parametrized: typing.Counter[int], + default_dict_bare: typing.DefaultDict, + default_dict_parametrized: typing.DefaultDict[str, int], + deque_bare: typing.Deque, + deque_parametrized: typing.Deque[str], + ordered_dict_bare: typing.OrderedDict, + ordered_dict_parametrized: typing.OrderedDict[int, str], ): - reveal_type(a) # revealed: @Todo(Unsupported or invalid type in a type expression) - reveal_type(b) # revealed: @Todo(typing.List alias) - reveal_type(c) # revealed: @Todo(Unsupported or invalid type in a type expression) - reveal_type(d) # revealed: @Todo(typing.Dict alias) - reveal_type(e) # revealed: @Todo(Unsupported or invalid type in a type expression) - reveal_type(f) # revealed: @Todo(typing.DefaultDict[] alias) - reveal_type(g) # revealed: @Todo(Unsupported or invalid type in a type expression) - reveal_type(h) # revealed: @Todo(typing.Set alias) - reveal_type(i) # revealed: @Todo(Unsupported or invalid type in a type expression) - reveal_type(j) # revealed: @Todo(typing.FrozenSet alias) - reveal_type(k) # revealed: @Todo(Unsupported or invalid type in a type expression) - reveal_type(l) # revealed: @Todo(typing.OrderedDict alias) - reveal_type(m) # revealed: @Todo(Unsupported or invalid type in a type expression) - reveal_type(n) # revealed: @Todo(typing.Counter[] alias) + reveal_type(list_bare) # revealed: list + reveal_type(list_parametrized) # revealed: list + + reveal_type(dict_bare) # revealed: dict + reveal_type(dict_parametrized) # revealed: dict + + reveal_type(set_bare) # revealed: set + reveal_type(set_parametrized) # revealed: set + + reveal_type(frozen_set_bare) # revealed: frozenset + reveal_type(frozen_set_parametrized) # revealed: frozenset + + reveal_type(chain_map_bare) # revealed: ChainMap + reveal_type(chain_map_parametrized) # revealed: ChainMap + + reveal_type(counter_bare) # revealed: Counter + reveal_type(counter_parametrized) # revealed: Counter + + reveal_type(default_dict_bare) # revealed: defaultdict + reveal_type(default_dict_parametrized) # revealed: defaultdict + + reveal_type(deque_bare) # revealed: deque + reveal_type(deque_parametrized) # revealed: deque + + reveal_type(ordered_dict_bare) # revealed: OrderedDict + reveal_type(ordered_dict_parametrized) # revealed: OrderedDict ``` ## Inheritance @@ -49,35 +65,63 @@ The aliases can be inherited from. Some of these are still partially or wholly T ```py import typing -class A(typing.Dict): ... +#################### +### Built-ins + +class ListSubclass(typing.List): ... # TODO: should have `Generic`, should not have `Unknown` -reveal_type(A.__mro__) # revealed: tuple[Literal[A], Literal[dict], Unknown, Literal[object]] +# revealed: tuple[Literal[ListSubclass], Literal[list], Unknown, Literal[object]] +reveal_type(ListSubclass.__mro__) -class B(typing.List): ... +class DictSubclass(typing.Dict): ... # TODO: should have `Generic`, should not have `Unknown` -reveal_type(B.__mro__) # revealed: tuple[Literal[B], Literal[list], Unknown, Literal[object]] +# revealed: tuple[Literal[DictSubclass], Literal[dict], Unknown, Literal[object]] +reveal_type(DictSubclass.__mro__) -class C(typing.Set): ... +class SetSubclass(typing.Set): ... # TODO: should have `Generic`, should not have `Unknown` -reveal_type(C.__mro__) # revealed: tuple[Literal[C], Literal[set], Unknown, Literal[object]] +# revealed: tuple[Literal[SetSubclass], Literal[set], Unknown, Literal[object]] +reveal_type(SetSubclass.__mro__) -class D(typing.FrozenSet): ... +class FrozenSetSubclass(typing.FrozenSet): ... # TODO: should have `Generic`, should not have `Unknown` -reveal_type(D.__mro__) # revealed: tuple[Literal[D], Literal[frozenset], Unknown, Literal[object]] +# revealed: tuple[Literal[FrozenSetSubclass], Literal[frozenset], Unknown, Literal[object]] +reveal_type(FrozenSetSubclass.__mro__) + +#################### +### `collections` + +class ChainMapSubclass(typing.ChainMap): ... + +# TODO: Should be (ChainMapSubclass, ChainMap, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object) +# revealed: tuple[Literal[ChainMapSubclass], Literal[ChainMap], Unknown, Literal[object]] +reveal_type(ChainMapSubclass.__mro__) + +class CounterSubclass(typing.Counter): ... + +# TODO: Should be (CounterSubclass, Counter, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object) +# revealed: tuple[Literal[CounterSubclass], Literal[Counter], Unknown, Literal[object]] +reveal_type(CounterSubclass.__mro__) -class E(typing.DefaultDict): ... +class DefaultDictSubclass(typing.DefaultDict): ... -reveal_type(E.__mro__) # revealed: tuple[Literal[E], @Todo(Support for more typing aliases as base classes), Literal[object]] +# TODO: Should be (DefaultDictSubclass, defaultdict, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object) +# revealed: tuple[Literal[DefaultDictSubclass], Literal[defaultdict], Unknown, Literal[object]] +reveal_type(DefaultDictSubclass.__mro__) -class F(typing.OrderedDict): ... +class DequeSubclass(typing.Deque): ... -reveal_type(F.__mro__) # revealed: tuple[Literal[F], @Todo(Support for more typing aliases as base classes), Literal[object]] +# TODO: Should be (DequeSubclass, deque, MutableSequence, Sequence, Reversible, Collection, Sized, Iterable, Container, Generic, object) +# revealed: tuple[Literal[DequeSubclass], Literal[deque], Unknown, Literal[object]] +reveal_type(DequeSubclass.__mro__) -class G(typing.Counter): ... +class OrderedDictSubclass(typing.OrderedDict): ... -reveal_type(G.__mro__) # revealed: tuple[Literal[G], @Todo(Support for more typing aliases as base classes), Literal[object]] +# TODO: Should be (OrderedDictSubclass, OrderedDict, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object) +# revealed: tuple[Literal[OrderedDictSubclass], Literal[OrderedDict], Unknown, Literal[object]] +reveal_type(OrderedDictSubclass.__mro__) ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md index 6b56df44b2073..37d9dfb7d43c3 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md @@ -51,7 +51,7 @@ class D(TypeIs): ... # error: [invalid-base] class E(Concatenate): ... # error: [invalid-base] class F(Callable): ... -reveal_type(F.__mro__) # revealed: tuple[Literal[F], @Todo(Support for more typing aliases as base classes), Literal[object]] +reveal_type(F.__mro__) # revealed: tuple[Literal[F], @Todo(Support for Callable as a base class), Literal[object]] ``` ## Subscriptability diff --git a/crates/red_knot_python_semantic/src/stdlib.rs b/crates/red_knot_python_semantic/src/stdlib.rs index 56f3c984bcbc2..2ab2fa2331434 100644 --- a/crates/red_knot_python_semantic/src/stdlib.rs +++ b/crates/red_knot_python_semantic/src/stdlib.rs @@ -17,6 +17,7 @@ pub(crate) enum CoreStdlibModule { Sys, #[allow(dead_code)] Abc, // currently only used in tests + Collections, } impl CoreStdlibModule { @@ -29,6 +30,7 @@ impl CoreStdlibModule { Self::TypingExtensions => "typing_extensions", Self::Sys => "sys", Self::Abc => "abc", + Self::Collections => "collections", } } diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 3bb335cacf180..19d645800b9a9 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -1393,6 +1393,11 @@ impl<'db> Type<'db> { | KnownClass::ModuleType | KnownClass::FunctionType | KnownClass::SpecialForm + | KnownClass::ChainMap + | KnownClass::Counter + | KnownClass::DefaultDict + | KnownClass::Deque + | KnownClass::OrderedDict | KnownClass::StdlibAlias | KnownClass::TypeVar, ) => false, @@ -1928,6 +1933,28 @@ impl<'db> Type<'db> { // We treat `typing.Type` exactly the same as `builtins.type`: Type::KnownInstance(KnownInstanceType::Type) => Ok(KnownClass::Type.to_instance(db)), Type::KnownInstance(KnownInstanceType::Tuple) => Ok(KnownClass::Tuple.to_instance(db)), + + // Legacy `typing` aliases + Type::KnownInstance(KnownInstanceType::List) => Ok(KnownClass::List.to_instance(db)), + Type::KnownInstance(KnownInstanceType::Dict) => Ok(KnownClass::Dict.to_instance(db)), + Type::KnownInstance(KnownInstanceType::Set) => Ok(KnownClass::Set.to_instance(db)), + Type::KnownInstance(KnownInstanceType::FrozenSet) => { + Ok(KnownClass::FrozenSet.to_instance(db)) + } + Type::KnownInstance(KnownInstanceType::ChainMap) => { + Ok(KnownClass::ChainMap.to_instance(db)) + } + Type::KnownInstance(KnownInstanceType::Counter) => { + Ok(KnownClass::Counter.to_instance(db)) + } + Type::KnownInstance(KnownInstanceType::DefaultDict) => { + Ok(KnownClass::DefaultDict.to_instance(db)) + } + Type::KnownInstance(KnownInstanceType::Deque) => Ok(KnownClass::Deque.to_instance(db)), + Type::KnownInstance(KnownInstanceType::OrderedDict) => { + Ok(KnownClass::OrderedDict.to_instance(db)) + } + Type::Union(union) => { let mut builder = UnionBuilder::new(db); let mut invalid_expressions = smallvec::SmallVec::default(); @@ -2189,6 +2216,12 @@ pub enum KnownClass { TypeVar, TypeAliasType, NoDefaultType, + // Collections + ChainMap, + Counter, + DefaultDict, + Deque, + OrderedDict, // sys VersionInfo, } @@ -2219,6 +2252,11 @@ impl<'db> KnownClass { Self::TypeVar => "TypeVar", Self::TypeAliasType => "TypeAliasType", Self::NoDefaultType => "_NoDefaultType", + Self::ChainMap => "ChainMap", + Self::Counter => "Counter", + Self::DefaultDict => "defaultdict", + Self::Deque => "deque", + Self::OrderedDict => "OrderedDict", // For example, `typing.List` is defined as `List = _Alias()` in typeshed Self::StdlibAlias => "_Alias", // This is the name the type of `sys.version_info` has in typeshed, @@ -2282,6 +2320,11 @@ impl<'db> KnownClass { CoreStdlibModule::TypingExtensions } } + Self::ChainMap + | Self::Counter + | Self::DefaultDict + | Self::Deque + | Self::OrderedDict => CoreStdlibModule::Collections, } } @@ -2309,6 +2352,11 @@ impl<'db> KnownClass { | Self::ModuleType | Self::FunctionType | Self::SpecialForm + | Self::ChainMap + | Self::Counter + | Self::DefaultDict + | Self::Deque + | Self::OrderedDict | Self::StdlibAlias | Self::BaseException | Self::BaseExceptionGroup @@ -2341,6 +2389,11 @@ impl<'db> KnownClass { "ModuleType" => Self::ModuleType, "FunctionType" => Self::FunctionType, "TypeAliasType" => Self::TypeAliasType, + "ChainMap" => Self::ChainMap, + "Counter" => Self::Counter, + "defaultdict" => Self::DefaultDict, + "deque" => Self::Deque, + "OrderedDict" => Self::OrderedDict, "_Alias" => Self::StdlibAlias, "_SpecialForm" => Self::SpecialForm, "_NoDefaultType" => Self::NoDefaultType, @@ -2372,6 +2425,11 @@ impl<'db> KnownClass { | Self::Dict | Self::Slice | Self::GenericAlias + | Self::ChainMap + | Self::Counter + | Self::DefaultDict + | Self::Deque + | Self::OrderedDict | Self::StdlibAlias // no equivalent class exists in typing_extensions, nor ever will | Self::ModuleType | Self::VersionInfo @@ -2407,6 +2465,24 @@ pub enum KnownInstanceType<'db> { Any, /// The symbol `typing.Tuple` (which can also be found as `typing_extensions.Tuple`) Tuple, + /// The symbol `typing.List` (which can also be found as `typing_extensions.List`) + List, + /// The symbol `typing.Dict` (which can also be found as `typing_extensions.Dict`) + Dict, + /// The symbol `typing.Set` (which can also be found as `typing_extensions.Set`) + Set, + /// The symbol `typing.FrozenSet` (which can also be found as `typing_extensions.FrozenSet`) + FrozenSet, + /// The symbol `typing.ChainMap` (which can also be found as `typing_extensions.ChainMap`) + ChainMap, + /// The symbol `typing.Counter` (which can also be found as `typing_extensions.Counter`) + Counter, + /// The symbol `typing.DefaultDict` (which can also be found as `typing_extensions.DefaultDict`) + DefaultDict, + /// The symbol `typing.Deque` (which can also be found as `typing_extensions.Deque`) + Deque, + /// The symbol `typing.OrderedDict` (which can also be found as `typing_extensions.OrderedDict`) + OrderedDict, /// The symbol `typing.Type` (which can also be found as `typing_extensions.Type`) Type, /// A single instance of `typing.TypeVar` @@ -2427,15 +2503,6 @@ pub enum KnownInstanceType<'db> { TypeAlias, TypeGuard, TypeIs, - List, - Dict, - DefaultDict, - Set, - FrozenSet, - Counter, - Deque, - ChainMap, - OrderedDict, ReadOnly, // TODO: fill this enum out with more special forms, etc. } diff --git a/crates/red_knot_python_semantic/src/types/class_base.rs b/crates/red_knot_python_semantic/src/types/class_base.rs index 850b1996e85ab..ede1e5e1c91b3 100644 --- a/crates/red_knot_python_semantic/src/types/class_base.rs +++ b/crates/red_knot_python_semantic/src/types/class_base.rs @@ -112,15 +112,24 @@ impl<'db> ClassBase<'db> { KnownInstanceType::FrozenSet => { Self::try_from_ty(db, KnownClass::FrozenSet.to_class_literal(db)) } - KnownInstanceType::Callable - | KnownInstanceType::ChainMap - | KnownInstanceType::Counter - | KnownInstanceType::DefaultDict - | KnownInstanceType::Deque - | KnownInstanceType::OrderedDict => Self::try_from_ty( - db, - todo_type!("Support for more typing aliases as base classes"), - ), + KnownInstanceType::ChainMap => { + Self::try_from_ty(db, KnownClass::ChainMap.to_class_literal(db)) + } + KnownInstanceType::Counter => { + Self::try_from_ty(db, KnownClass::Counter.to_class_literal(db)) + } + KnownInstanceType::DefaultDict => { + Self::try_from_ty(db, KnownClass::DefaultDict.to_class_literal(db)) + } + KnownInstanceType::Deque => { + Self::try_from_ty(db, KnownClass::Deque.to_class_literal(db)) + } + KnownInstanceType::OrderedDict => { + Self::try_from_ty(db, KnownClass::OrderedDict.to_class_literal(db)) + } + KnownInstanceType::Callable => { + Self::try_from_ty(db, todo_type!("Support for Callable as a base class")) + } }, } } diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index e7d80e8495922..d0338d7c08814 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -4938,42 +4938,45 @@ impl<'db> TypeInferenceBuilder<'db> { self.infer_type_expression(arguments_slice); todo_type!("Callable types") } + + // TODO: Generics KnownInstanceType::ChainMap => { self.infer_type_expression(arguments_slice); - todo_type!("typing.ChainMap alias") + KnownClass::ChainMap.to_instance(self.db) } KnownInstanceType::OrderedDict => { self.infer_type_expression(arguments_slice); - todo_type!("typing.OrderedDict alias") + KnownClass::OrderedDict.to_instance(self.db) } KnownInstanceType::Dict => { self.infer_type_expression(arguments_slice); - todo_type!("typing.Dict alias") + KnownClass::Dict.to_instance(self.db) } KnownInstanceType::List => { self.infer_type_expression(arguments_slice); - todo_type!("typing.List alias") + KnownClass::List.to_instance(self.db) } KnownInstanceType::DefaultDict => { self.infer_type_expression(arguments_slice); - todo_type!("typing.DefaultDict[] alias") + KnownClass::DefaultDict.to_instance(self.db) } KnownInstanceType::Counter => { self.infer_type_expression(arguments_slice); - todo_type!("typing.Counter[] alias") + KnownClass::Counter.to_instance(self.db) } KnownInstanceType::Set => { self.infer_type_expression(arguments_slice); - todo_type!("typing.Set alias") + KnownClass::Set.to_instance(self.db) } KnownInstanceType::FrozenSet => { self.infer_type_expression(arguments_slice); - todo_type!("typing.FrozenSet alias") + KnownClass::FrozenSet.to_instance(self.db) } KnownInstanceType::Deque => { self.infer_type_expression(arguments_slice); - todo_type!("typing.Deque alias") + KnownClass::Deque.to_instance(self.db) } + KnownInstanceType::ReadOnly => { self.infer_type_expression(arguments_slice); todo_type!("Required[] type qualifier")