Skip to content

Commit

Permalink
[red-knot] Basic support for other legacy typing aliases (#14998)
Browse files Browse the repository at this point in the history
## Summary

Resolves #14997.

## Test Plan

Markdown tests.
  • Loading branch information
InSyncWithFoo authored Dec 17, 2024
1 parent 867a8f9 commit 7c2e7cf
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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__)
```
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions crates/red_knot_python_semantic/src/stdlib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub(crate) enum CoreStdlibModule {
Sys,
#[allow(dead_code)]
Abc, // currently only used in tests
Collections,
}

impl CoreStdlibModule {
Expand All @@ -29,6 +30,7 @@ impl CoreStdlibModule {
Self::TypingExtensions => "typing_extensions",
Self::Sys => "sys",
Self::Abc => "abc",
Self::Collections => "collections",
}
}

Expand Down
85 changes: 76 additions & 9 deletions crates/red_knot_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -2189,6 +2216,12 @@ pub enum KnownClass {
TypeVar,
TypeAliasType,
NoDefaultType,
// Collections
ChainMap,
Counter,
DefaultDict,
Deque,
OrderedDict,
// sys
VersionInfo,
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -2282,6 +2320,11 @@ impl<'db> KnownClass {
CoreStdlibModule::TypingExtensions
}
}
Self::ChainMap
| Self::Counter
| Self::DefaultDict
| Self::Deque
| Self::OrderedDict => CoreStdlibModule::Collections,
}
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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`
Expand All @@ -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.
}
Expand Down
27 changes: 18 additions & 9 deletions crates/red_knot_python_semantic/src/types/class_base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
}
},
}
}
Expand Down
Loading

0 comments on commit 7c2e7cf

Please sign in to comment.