Skip to content

Commit f23d2c9

Browse files
[ty] Support using legacy typing aliases for generic classes in type annotations (#18404)
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
1 parent 67d94d9 commit f23d2c9

File tree

4 files changed

+158
-63
lines changed

4 files changed

+158
-63
lines changed

crates/ty_python_semantic/resources/mdtest/annotations/stdlib_typing_aliases.md

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,41 +31,81 @@ def f(
3131
ordered_dict_parametrized: typing.OrderedDict[int, str],
3232
):
3333
reveal_type(list_bare) # revealed: list[Unknown]
34-
# TODO: revealed: list[int]
35-
reveal_type(list_parametrized) # revealed: list[Unknown]
34+
reveal_type(list_parametrized) # revealed: list[int]
3635

3736
reveal_type(dict_bare) # revealed: dict[Unknown, Unknown]
38-
# TODO: revealed: dict[int, str]
39-
reveal_type(dict_parametrized) # revealed: dict[Unknown, Unknown]
37+
reveal_type(dict_parametrized) # revealed: dict[int, str]
4038

4139
reveal_type(set_bare) # revealed: set[Unknown]
42-
# TODO: revealed: set[int]
43-
reveal_type(set_parametrized) # revealed: set[Unknown]
40+
reveal_type(set_parametrized) # revealed: set[int]
4441

45-
# TODO: revealed: frozenset[Unknown]
4642
reveal_type(frozen_set_bare) # revealed: frozenset[Unknown]
47-
# TODO: revealed: frozenset[str]
48-
reveal_type(frozen_set_parametrized) # revealed: frozenset[Unknown]
43+
reveal_type(frozen_set_parametrized) # revealed: frozenset[str]
4944

5045
reveal_type(chain_map_bare) # revealed: ChainMap[Unknown, Unknown]
51-
# TODO: revealed: ChainMap[str, int]
52-
reveal_type(chain_map_parametrized) # revealed: ChainMap[Unknown, Unknown]
46+
reveal_type(chain_map_parametrized) # revealed: ChainMap[str, int]
5347

5448
reveal_type(counter_bare) # revealed: Counter[Unknown]
55-
# TODO: revealed: Counter[int]
56-
reveal_type(counter_parametrized) # revealed: Counter[Unknown]
49+
reveal_type(counter_parametrized) # revealed: Counter[int]
5750

5851
reveal_type(default_dict_bare) # revealed: defaultdict[Unknown, Unknown]
59-
# TODO: revealed: defaultdict[str, int]
60-
reveal_type(default_dict_parametrized) # revealed: defaultdict[Unknown, Unknown]
52+
reveal_type(default_dict_parametrized) # revealed: defaultdict[str, int]
6153

6254
reveal_type(deque_bare) # revealed: deque[Unknown]
63-
# TODO: revealed: deque[str]
64-
reveal_type(deque_parametrized) # revealed: deque[Unknown]
55+
reveal_type(deque_parametrized) # revealed: deque[str]
6556

6657
reveal_type(ordered_dict_bare) # revealed: OrderedDict[Unknown, Unknown]
67-
# TODO: revealed: OrderedDict[int, str]
68-
reveal_type(ordered_dict_parametrized) # revealed: OrderedDict[Unknown, Unknown]
58+
reveal_type(ordered_dict_parametrized) # revealed: OrderedDict[int, str]
59+
```
60+
61+
## Incorrect number of type arguments
62+
63+
In case the incorrect number of type arguments is passed, a diagnostic is given.
64+
65+
```py
66+
import typing
67+
68+
def f(
69+
# error: [invalid-type-form] "Legacy alias `typing.List` expected exactly 1 argument, got 2"
70+
incorrect_list: typing.List[int, int],
71+
# error: [invalid-type-form] "Legacy alias `typing.Dict` expected exactly 2 arguments, got 3"
72+
incorrect_dict: typing.Dict[int, int, int],
73+
# error: [invalid-type-form] "Legacy alias `typing.Dict` expected exactly 2 arguments, got 1"
74+
incorrect_dict2: typing.Dict[int], # type argument is not a tuple here
75+
# error: [invalid-type-form]
76+
incorrect_set: typing.Set[int, int],
77+
# error: [invalid-type-form]
78+
incorrect_frozen_set: typing.FrozenSet[int, int],
79+
# error: [invalid-type-form]
80+
incorrect_chain_map: typing.ChainMap[int, int, int],
81+
# error: [invalid-type-form]
82+
incorrect_chain_map2: typing.ChainMap[int],
83+
# error: [invalid-type-form]
84+
incorrect_counter: typing.Counter[int, int],
85+
# error: [invalid-type-form]
86+
incorrect_default_dict: typing.DefaultDict[int, int, int],
87+
# error: [invalid-type-form]
88+
incorrect_default_dict2: typing.DefaultDict[int],
89+
# error: [invalid-type-form]
90+
incorrect_deque: typing.Deque[int, int],
91+
# error: [invalid-type-form]
92+
incorrect_ordered_dict: typing.OrderedDict[int, int, int],
93+
# error: [invalid-type-form]
94+
incorrect_ordered_dict2: typing.OrderedDict[int],
95+
):
96+
reveal_type(incorrect_list) # revealed: list[Unknown]
97+
reveal_type(incorrect_dict) # revealed: dict[Unknown, Unknown]
98+
reveal_type(incorrect_dict2) # revealed: dict[Unknown, Unknown]
99+
reveal_type(incorrect_set) # revealed: set[Unknown]
100+
reveal_type(incorrect_frozen_set) # revealed: frozenset[Unknown]
101+
reveal_type(incorrect_chain_map) # revealed: ChainMap[Unknown, Unknown]
102+
reveal_type(incorrect_chain_map2) # revealed: ChainMap[Unknown, Unknown]
103+
reveal_type(incorrect_counter) # revealed: Counter[Unknown]
104+
reveal_type(incorrect_default_dict) # revealed: defaultdict[Unknown, Unknown]
105+
reveal_type(incorrect_default_dict2) # revealed: defaultdict[Unknown, Unknown]
106+
reveal_type(incorrect_deque) # revealed: deque[Unknown]
107+
reveal_type(incorrect_ordered_dict) # revealed: OrderedDict[Unknown, Unknown]
108+
reveal_type(incorrect_ordered_dict2) # revealed: OrderedDict[Unknown, Unknown]
69109
```
70110

71111
## Inheritance

crates/ty_python_semantic/resources/mdtest/generics/builtins.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,13 @@ class dict[K, V, Extra]: ...
2424
def reveal_type(obj, /): ...
2525
```
2626

27-
If we don't, then we won't be able to infer the types of variadic keyword arguments correctly.
27+
If we don't, then we may get "surprising" results when inferring the types of variadic keyword
28+
arguments.
2829

2930
```py
3031
def f(**kwargs):
31-
reveal_type(kwargs) # revealed: Unknown
32+
reveal_type(kwargs) # revealed: dict[Unknown, Unknown, Unknown]
3233

3334
def g(**kwargs: int):
34-
reveal_type(kwargs) # revealed: Unknown
35+
reveal_type(kwargs) # revealed: dict[Unknown, Unknown, Unknown]
3536
```

crates/ty_python_semantic/src/types/class.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2426,7 +2426,7 @@ impl<'db> KnownClass {
24262426
return Type::unknown();
24272427
};
24282428
let Some(generic_context) = class_literal.generic_context(db) else {
2429-
return Type::unknown();
2429+
return Type::instance(db, ClassType::NonGeneric(class_literal));
24302430
};
24312431

24322432
let types = specialization.into_iter().collect::<Box<[_]>>();
@@ -2437,11 +2437,11 @@ impl<'db> KnownClass {
24372437
if MESSAGES.lock().unwrap().insert(self) {
24382438
tracing::info!(
24392439
"Wrong number of types when specializing {}. \
2440-
Falling back to `Unknown` for the symbol instead.",
2440+
Falling back to default specialization for the symbol instead.",
24412441
self.display(db)
24422442
);
24432443
}
2444-
return Type::unknown();
2444+
return Type::instance(db, class_literal.default_specialization(db));
24452445
}
24462446

24472447
let specialization = generic_context.specialize(db, types);

crates/ty_python_semantic/src/types/infer.rs

Lines changed: 92 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7975,7 +7975,7 @@ impl<'db> TypeInferenceBuilder<'db> {
79757975

79767976
match value_ty {
79777977
Type::SpecialForm(SpecialFormType::Annotated) => {
7978-
// This branch is similar to the corresponding branch in `infer_parameterized_known_instance_type_expression`, but
7978+
// This branch is similar to the corresponding branch in `infer_parameterized_special_form_type_expression`, but
79797979
// `Annotated[…]` can appear both in annotation expressions and in type expressions, and needs to be handled slightly
79807980
// differently in each case (calling either `infer_type_expression_*` or `infer_annotation_expression_*`).
79817981
if let ast::Expr::Tuple(ast::ExprTuple {
@@ -8701,6 +8701,43 @@ impl<'db> TypeInferenceBuilder<'db> {
87018701
}
87028702
}
87038703

8704+
fn infer_parameterized_legacy_typing_alias(
8705+
&mut self,
8706+
subscript_node: &ast::ExprSubscript,
8707+
expected_arg_count: usize,
8708+
alias: SpecialFormType,
8709+
class: KnownClass,
8710+
) -> Type<'db> {
8711+
let arguments = &*subscript_node.slice;
8712+
let (args, args_number) = if let ast::Expr::Tuple(t) = arguments {
8713+
(Either::Left(t), t.len())
8714+
} else {
8715+
(Either::Right([arguments]), 1)
8716+
};
8717+
if args_number != expected_arg_count {
8718+
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript_node) {
8719+
let noun = if expected_arg_count == 1 {
8720+
"argument"
8721+
} else {
8722+
"arguments"
8723+
};
8724+
builder.into_diagnostic(format_args!(
8725+
"Legacy alias `{alias}` expected exactly {expected_arg_count} {noun}, \
8726+
got {args_number}",
8727+
));
8728+
}
8729+
}
8730+
let ty = class.to_specialized_instance(
8731+
self.db(),
8732+
args.into_iter()
8733+
.map(|node| self.infer_type_expression(node)),
8734+
);
8735+
if arguments.is_tuple_expr() {
8736+
self.store_expression_type(arguments, ty);
8737+
}
8738+
ty
8739+
}
8740+
87048741
fn infer_parameterized_special_form_type_expression(
87058742
&mut self,
87068743
subscript: &ast::ExprSubscript,
@@ -8916,43 +8953,60 @@ impl<'db> TypeInferenceBuilder<'db> {
89168953
}
89178954
},
89188955

8919-
// TODO: Generics
8920-
SpecialFormType::ChainMap => {
8921-
self.infer_type_expression(arguments_slice);
8922-
KnownClass::ChainMap.to_instance(db)
8923-
}
8924-
SpecialFormType::OrderedDict => {
8925-
self.infer_type_expression(arguments_slice);
8926-
KnownClass::OrderedDict.to_instance(db)
8927-
}
8928-
SpecialFormType::Dict => {
8929-
self.infer_type_expression(arguments_slice);
8930-
KnownClass::Dict.to_instance(db)
8931-
}
8932-
SpecialFormType::List => {
8933-
self.infer_type_expression(arguments_slice);
8934-
KnownClass::List.to_instance(db)
8935-
}
8936-
SpecialFormType::DefaultDict => {
8937-
self.infer_type_expression(arguments_slice);
8938-
KnownClass::DefaultDict.to_instance(db)
8939-
}
8940-
SpecialFormType::Counter => {
8941-
self.infer_type_expression(arguments_slice);
8942-
KnownClass::Counter.to_instance(db)
8943-
}
8944-
SpecialFormType::Set => {
8945-
self.infer_type_expression(arguments_slice);
8946-
KnownClass::Set.to_instance(db)
8947-
}
8948-
SpecialFormType::FrozenSet => {
8949-
self.infer_type_expression(arguments_slice);
8950-
KnownClass::FrozenSet.to_instance(db)
8951-
}
8952-
SpecialFormType::Deque => {
8953-
self.infer_type_expression(arguments_slice);
8954-
KnownClass::Deque.to_instance(db)
8955-
}
8956+
SpecialFormType::ChainMap => self.infer_parameterized_legacy_typing_alias(
8957+
subscript,
8958+
2,
8959+
SpecialFormType::ChainMap,
8960+
KnownClass::ChainMap,
8961+
),
8962+
SpecialFormType::OrderedDict => self.infer_parameterized_legacy_typing_alias(
8963+
subscript,
8964+
2,
8965+
SpecialFormType::OrderedDict,
8966+
KnownClass::OrderedDict,
8967+
),
8968+
SpecialFormType::Dict => self.infer_parameterized_legacy_typing_alias(
8969+
subscript,
8970+
2,
8971+
SpecialFormType::Dict,
8972+
KnownClass::Dict,
8973+
),
8974+
SpecialFormType::List => self.infer_parameterized_legacy_typing_alias(
8975+
subscript,
8976+
1,
8977+
SpecialFormType::List,
8978+
KnownClass::List,
8979+
),
8980+
SpecialFormType::DefaultDict => self.infer_parameterized_legacy_typing_alias(
8981+
subscript,
8982+
2,
8983+
SpecialFormType::DefaultDict,
8984+
KnownClass::DefaultDict,
8985+
),
8986+
SpecialFormType::Counter => self.infer_parameterized_legacy_typing_alias(
8987+
subscript,
8988+
1,
8989+
SpecialFormType::Counter,
8990+
KnownClass::Counter,
8991+
),
8992+
SpecialFormType::Set => self.infer_parameterized_legacy_typing_alias(
8993+
subscript,
8994+
1,
8995+
SpecialFormType::Set,
8996+
KnownClass::Set,
8997+
),
8998+
SpecialFormType::FrozenSet => self.infer_parameterized_legacy_typing_alias(
8999+
subscript,
9000+
1,
9001+
SpecialFormType::FrozenSet,
9002+
KnownClass::FrozenSet,
9003+
),
9004+
SpecialFormType::Deque => self.infer_parameterized_legacy_typing_alias(
9005+
subscript,
9006+
1,
9007+
SpecialFormType::Deque,
9008+
KnownClass::Deque,
9009+
),
89569010

89579011
SpecialFormType::ReadOnly => {
89589012
self.infer_type_expression(arguments_slice);

0 commit comments

Comments
 (0)