Skip to content

Commit 8c80d76

Browse files
committed
[ty] Generic implicit types aliases
1 parent 6b7adb0 commit 8c80d76

File tree

5 files changed

+216
-93
lines changed

5 files changed

+216
-93
lines changed

crates/ty_python_semantic/resources/mdtest/async.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,8 @@ async def main():
7979
task("B"),
8080
)
8181

82-
# TODO: these should be `int`
83-
reveal_type(a) # revealed: Unknown
84-
reveal_type(b) # revealed: Unknown
82+
reveal_type(a) # revealed: int
83+
reveal_type(b) # revealed: int
8584
```
8685

8786
## Under the hood

crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md

Lines changed: 49 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,8 @@ ListOrTuple = list[T] | tuple[T, ...]
388388
ListOrTupleLegacy = Union[list[T], tuple[T, ...]]
389389
MyCallable = Callable[P, T]
390390
AnnotatedType = Annotated[T, "tag"]
391+
TransparentAlias = T
392+
MyOptional = T | None
391393

392394
# TODO: Consider displaying this as `<class 'list[T]'>`, … instead? (and similar for some others below)
393395
reveal_type(MyList) # revealed: <class 'list[typing.TypeVar]'>
@@ -400,43 +402,40 @@ reveal_type(ListOrTuple) # revealed: types.UnionType
400402
reveal_type(ListOrTupleLegacy) # revealed: types.UnionType
401403
reveal_type(MyCallable) # revealed: GenericAlias
402404
reveal_type(AnnotatedType) # revealed: <typing.Annotated special form>
405+
reveal_type(TransparentAlias) # revealed: typing.TypeVar
406+
reveal_type(MyOptional) # revealed: types.UnionType
403407

404408
def _(
405409
list_of_ints: MyList[int],
406410
dict_str_to_int: MyDict[str, int],
407-
# TODO: no error here
408-
# error: [invalid-type-form] "`typing.TypeVar` is not a generic class"
409411
subclass_of_int: MyType[int],
410412
int_and_str: IntAndType[str],
411413
pair_of_ints: Pair[int],
412414
int_and_bytes: Sum[int, bytes],
413415
list_or_tuple: ListOrTuple[int],
414416
list_or_tuple_legacy: ListOrTupleLegacy[int],
415-
# TODO: no error here
417+
# TODO: no errors here
416418
# error: [invalid-type-form] "List literals are not allowed in this context in a type expression: Did you mean `tuple[str, bytes]`?"
419+
# error: [too-many-positional-arguments] "Too many positional arguments: expected 1, got 2"
417420
my_callable: MyCallable[[str, bytes], int],
418421
annotated_int: AnnotatedType[int],
422+
transparent_alias: TransparentAlias[int],
423+
optional_int: MyOptional[int],
419424
):
420-
# TODO: This should be `list[int]`
421-
reveal_type(list_of_ints) # revealed: @Todo(specialized generic alias in type expression)
422-
# TODO: This should be `dict[str, int]`
423-
reveal_type(dict_str_to_int) # revealed: @Todo(specialized generic alias in type expression)
424-
# TODO: This should be `type[int]`
425-
reveal_type(subclass_of_int) # revealed: Unknown
426-
# TODO: This should be `tuple[int, str]`
427-
reveal_type(int_and_str) # revealed: @Todo(specialized generic alias in type expression)
428-
# TODO: This should be `tuple[int, int]`
429-
reveal_type(pair_of_ints) # revealed: @Todo(specialized generic alias in type expression)
430-
# TODO: This should be `tuple[int, bytes]`
431-
reveal_type(int_and_bytes) # revealed: @Todo(specialized generic alias in type expression)
432-
# TODO: This should be `list[int] | tuple[int, ...]`
433-
reveal_type(list_or_tuple) # revealed: @Todo(Generic specialization of types.UnionType)
434-
# TODO: This should be `list[int] | tuple[int, ...]`
435-
reveal_type(list_or_tuple_legacy) # revealed: @Todo(Generic specialization of types.UnionType)
425+
reveal_type(list_of_ints) # revealed: list[int]
426+
reveal_type(dict_str_to_int) # revealed: dict[str, int]
427+
reveal_type(subclass_of_int) # revealed: type[int]
428+
reveal_type(int_and_str) # revealed: tuple[int, str]
429+
reveal_type(pair_of_ints) # revealed: tuple[int, int]
430+
reveal_type(int_and_bytes) # revealed: tuple[int, bytes]
431+
reveal_type(list_or_tuple) # revealed: list[int] | tuple[int, ...]
432+
reveal_type(list_or_tuple_legacy) # revealed: list[int] | tuple[int, ...]
433+
reveal_type(list_or_tuple_legacy) # revealed: list[int] | tuple[int, ...]
436434
# TODO: This should be `(str, bytes) -> int`
437-
reveal_type(my_callable) # revealed: @Todo(Generic specialization of typing.Callable)
438-
# TODO: This should be `int`
439-
reveal_type(annotated_int) # revealed: @Todo(Generic specialization of typing.Annotated)
435+
reveal_type(my_callable) # revealed: Unknown
436+
reveal_type(annotated_int) # revealed: int
437+
reveal_type(transparent_alias) # revealed: int
438+
reveal_type(optional_int) # revealed: int | None
440439
```
441440

442441
Generic implicit type aliases can be partially specialized:
@@ -446,15 +445,12 @@ U = TypeVar("U")
446445

447446
DictStrTo = MyDict[str, U]
448447

449-
reveal_type(DictStrTo) # revealed: GenericAlias
448+
reveal_type(DictStrTo) # revealed: <class 'dict[str, typing.TypeVar]'>
450449

451450
def _(
452-
# TODO: No error here
453-
# error: [invalid-type-form] "Invalid subscript of object of type `GenericAlias` in type expression"
454451
dict_str_to_int: DictStrTo[int],
455452
):
456-
# TODO: This should be `dict[str, int]`
457-
reveal_type(dict_str_to_int) # revealed: Unknown
453+
reveal_type(dict_str_to_int) # revealed: dict[str, int]
458454
```
459455

460456
Using specializations of generic implicit type aliases in other implicit type aliases works as
@@ -465,25 +461,31 @@ IntsOrNone = MyList[int] | None
465461
IntsOrStrs = Pair[int] | Pair[str]
466462
ListOfPairs = MyList[Pair[str]]
467463

468-
reveal_type(IntsOrNone) # revealed: UnionType
469-
reveal_type(IntsOrStrs) # revealed: UnionType
470-
reveal_type(ListOfPairs) # revealed: GenericAlias
464+
reveal_type(IntsOrNone) # revealed: types.UnionType
465+
reveal_type(IntsOrStrs) # revealed: types.UnionType
466+
reveal_type(ListOfPairs) # revealed: <class 'list[tuple[str, str]]'>
471467

472468
def _(
473-
# TODO: This should not be an error
474-
# error: [invalid-type-form] "Variable of type `UnionType` is not allowed in a type expression"
475469
ints_or_none: IntsOrNone,
476-
# TODO: This should not be an error
477-
# error: [invalid-type-form] "Variable of type `UnionType` is not allowed in a type expression"
478470
ints_or_strs: IntsOrStrs,
479471
list_of_pairs: ListOfPairs,
480472
):
481-
# TODO: This should be `list[int] | None`
482-
reveal_type(ints_or_none) # revealed: Unknown
483-
# TODO: This should be `tuple[int, int] | tuple[str, str]`
484-
reveal_type(ints_or_strs) # revealed: Unknown
485-
# TODO: This should be `list[tuple[str, str]]`
486-
reveal_type(list_of_pairs) # revealed: @Todo(Support for `typing.GenericAlias` instances in type expressions)
473+
reveal_type(ints_or_none) # revealed: list[int] | None
474+
reveal_type(ints_or_strs) # revealed: tuple[int, int] | tuple[str, str]
475+
reveal_type(list_of_pairs) # revealed: list[tuple[str, str]]
476+
```
477+
478+
A generic implicit type alias can also be used in another generic implicit type alias:
479+
480+
```py
481+
MyOtherList = MyList[T]
482+
483+
reveal_type(MyOtherList) # revealed: <class 'list[typing.TypeVar]'>
484+
485+
def _(
486+
list_of_ints: MyOtherList[int],
487+
):
488+
reveal_type(list_of_ints) # revealed: list[int]
487489
```
488490

489491
If a generic implicit type alias is used unspecialized in a type expression, we treat it as an
@@ -522,8 +524,6 @@ reveal_mro(Derived1)
522524

523525
GenericBaseAlias = GenericBase[T]
524526

525-
# TODO: No error here
526-
# error: [non-subscriptable] "Cannot subscript object of type `<class 'GenericBase[typing.TypeVar]'>` with no `__class_getitem__` method"
527527
class Derived2(GenericBaseAlias[int]):
528528
pass
529529
```
@@ -533,26 +533,23 @@ A generic alias that is already fully specialized cannot be specialized again:
533533
```py
534534
ListOfInts = list[int]
535535

536-
# TODO: this should be an error
536+
# error: [too-many-positional-arguments] "Too many positional arguments: expected 0, got 1"
537537
def _(doubly_specialized: ListOfInts[int]):
538-
# TODO: this should be `Unknown`
539-
reveal_type(doubly_specialized) # revealed: @Todo(specialized generic alias in type expression)
538+
reveal_type(doubly_specialized) # revealed: Unknown
540539
```
541540

542541
Specializing a generic implicit type alias with an incorrect number of type arguments also results
543542
in an error:
544543

545544
```py
546545
def _(
547-
# TODO: this should be an error
546+
# error: [too-many-positional-arguments] "Too many positional arguments: expected 1, got 2"
548547
list_too_many_args: MyList[int, str],
549-
# TODO: this should be an error
548+
# error: [missing-argument] "No argument provided for required parameter `U`"
550549
dict_too_few_args: MyDict[int],
551550
):
552-
# TODO: this should be `Unknown`
553-
reveal_type(list_too_many_args) # revealed: @Todo(specialized generic alias in type expression)
554-
# TODO: this should be `Unknown`
555-
reveal_type(dict_too_few_args) # revealed: @Todo(specialized generic alias in type expression)
551+
reveal_type(list_too_many_args) # revealed: Unknown
552+
reveal_type(dict_too_few_args) # revealed: Unknown
556553
```
557554

558555
## `Literal`s
@@ -642,8 +639,7 @@ Deprecated = Annotated[T, "deprecated attribute"]
642639
class C:
643640
old: Deprecated[int]
644641

645-
# TODO: Should be `int`
646-
reveal_type(C().old) # revealed: @Todo(Generic specialization of typing.Annotated)
642+
reveal_type(C().old) # revealed: int
647643
```
648644

649645
If the metadata argument is missing, we emit an error (because this code fails at runtime), but

crates/ty_python_semantic/src/types.rs

Lines changed: 102 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7165,17 +7165,70 @@ impl<'db> Type<'db> {
71657165
}
71667166
}
71677167

7168-
Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => match type_mapping {
7169-
TypeMapping::BindLegacyTypevars(binding_context) => {
7170-
Type::TypeVar(BoundTypeVarInstance::new(db, typevar, *binding_context))
7168+
Type::KnownInstance(known_instance) => match known_instance {
7169+
KnownInstanceType::TypeVar(typevar) => {
7170+
match type_mapping {
7171+
TypeMapping::BindLegacyTypevars(binding_context) => {
7172+
Type::TypeVar(BoundTypeVarInstance::new(db, typevar, *binding_context))
7173+
}
7174+
TypeMapping::Specialization(_) |
7175+
TypeMapping::PartialSpecialization(_) |
7176+
TypeMapping::PromoteLiterals(_) |
7177+
TypeMapping::BindSelf(_) |
7178+
TypeMapping::ReplaceSelf { .. } |
7179+
TypeMapping::Materialize(_) |
7180+
TypeMapping::ReplaceParameterDefaults => self,
7181+
}
71717182
}
7172-
TypeMapping::Specialization(_) |
7173-
TypeMapping::PartialSpecialization(_) |
7174-
TypeMapping::PromoteLiterals(_) |
7175-
TypeMapping::BindSelf(_) |
7176-
TypeMapping::ReplaceSelf { .. } |
7177-
TypeMapping::Materialize(_) |
7178-
TypeMapping::ReplaceParameterDefaults => self,
7183+
KnownInstanceType::UnionType(instance) => {
7184+
if let Ok(union_type) = instance.union_type(db) {
7185+
Type::KnownInstance(KnownInstanceType::UnionType(
7186+
UnionTypeInstance::new(
7187+
db,
7188+
instance._value_expr_types(db),
7189+
Ok(union_type.apply_type_mapping_impl(db, type_mapping, tcx, visitor)
7190+
)
7191+
)))
7192+
} else {
7193+
self
7194+
}
7195+
},
7196+
KnownInstanceType::Annotated(ty) => {
7197+
Type::KnownInstance(KnownInstanceType::Annotated(
7198+
InternedType::new(
7199+
db,
7200+
ty.inner(db).apply_type_mapping_impl(db, type_mapping, tcx, visitor),
7201+
)
7202+
))
7203+
},
7204+
KnownInstanceType::Callable(callable_type) => {
7205+
Type::KnownInstance(KnownInstanceType::Callable(
7206+
callable_type.apply_type_mapping_impl(db, type_mapping, tcx, visitor),
7207+
))
7208+
},
7209+
KnownInstanceType::TypeGenericAlias(ty) => {
7210+
Type::KnownInstance(KnownInstanceType::TypeGenericAlias(
7211+
InternedType::new(
7212+
db,
7213+
ty.inner(db).apply_type_mapping_impl(db, type_mapping, tcx, visitor),
7214+
)
7215+
))
7216+
},
7217+
7218+
KnownInstanceType::SubscriptedProtocol(_) |
7219+
KnownInstanceType::SubscriptedGeneric(_) |
7220+
KnownInstanceType::TypeAliasType(_) |
7221+
KnownInstanceType::Deprecated(_) |
7222+
KnownInstanceType::Field(_) |
7223+
KnownInstanceType::ConstraintSet(_) |
7224+
KnownInstanceType::GenericContext(_) |
7225+
KnownInstanceType::Specialization(_) |
7226+
KnownInstanceType::Literal(_) |
7227+
KnownInstanceType::LiteralStringAlias(_) |
7228+
KnownInstanceType::NewType(_) => {
7229+
// TODO: ?
7230+
self
7231+
},
71797232
}
71807233

71817234
Type::FunctionLiteral(function) => {
@@ -7336,8 +7389,7 @@ impl<'db> Type<'db> {
73367389
// some other generic context's specialization is applied to it.
73377390
| Type::ClassLiteral(_)
73387391
| Type::BoundSuper(_)
7339-
| Type::SpecialForm(_)
7340-
| Type::KnownInstance(_) => self,
7392+
| Type::SpecialForm(_) => self,
73417393
}
73427394
}
73437395

@@ -7474,6 +7526,44 @@ impl<'db> Type<'db> {
74747526
});
74757527
}
74767528

7529+
Type::KnownInstance(known_instance) => match known_instance {
7530+
KnownInstanceType::UnionType(instance) => {
7531+
if let Ok(union_type) = instance.union_type(db) {
7532+
union_type.find_legacy_typevars_impl(
7533+
db,
7534+
binding_context,
7535+
typevars,
7536+
visitor,
7537+
);
7538+
}
7539+
}
7540+
KnownInstanceType::Annotated(ty) => {
7541+
ty.inner(db)
7542+
.find_legacy_typevars_impl(db, binding_context, typevars, visitor);
7543+
}
7544+
KnownInstanceType::Callable(callable_type) => {
7545+
callable_type.find_legacy_typevars_impl(db, binding_context, typevars, visitor);
7546+
}
7547+
KnownInstanceType::TypeGenericAlias(ty) => {
7548+
ty.inner(db)
7549+
.find_legacy_typevars_impl(db, binding_context, typevars, visitor);
7550+
}
7551+
KnownInstanceType::SubscriptedProtocol(_)
7552+
| KnownInstanceType::SubscriptedGeneric(_)
7553+
| KnownInstanceType::TypeVar(_)
7554+
| KnownInstanceType::TypeAliasType(_)
7555+
| KnownInstanceType::Deprecated(_)
7556+
| KnownInstanceType::Field(_)
7557+
| KnownInstanceType::ConstraintSet(_)
7558+
| KnownInstanceType::GenericContext(_)
7559+
| KnownInstanceType::Specialization(_)
7560+
| KnownInstanceType::Literal(_)
7561+
| KnownInstanceType::LiteralStringAlias(_)
7562+
| KnownInstanceType::NewType(_) => {
7563+
// TODO?
7564+
}
7565+
},
7566+
74777567
Type::Dynamic(_)
74787568
| Type::Never
74797569
| Type::AlwaysTruthy
@@ -7501,7 +7591,6 @@ impl<'db> Type<'db> {
75017591
| Type::EnumLiteral(_)
75027592
| Type::BoundSuper(_)
75037593
| Type::SpecialForm(_)
7504-
| Type::KnownInstance(_)
75057594
| Type::TypedDict(_) => {}
75067595
}
75077596
}

crates/ty_python_semantic/src/types/infer/builder.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10714,6 +10714,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
1071410714

1071510715
fn infer_subscript_load(&mut self, subscript: &ast::ExprSubscript) -> Type<'db> {
1071610716
let value_ty = self.infer_expression(&subscript.value, TypeContext::default());
10717+
10718+
if value_ty.is_generic_alias() {
10719+
return self
10720+
.infer_explicitly_specialized_implicit_type_alias(subscript, value_ty, false);
10721+
}
10722+
1071710723
self.infer_subscript_load_impl(value_ty, subscript)
1071810724
}
1071910725

0 commit comments

Comments
 (0)