Skip to content

Commit 4f6d9af

Browse files
committed
Handle attribute expressions as well
1 parent 812d07b commit 4f6d9af

File tree

2 files changed

+135
-12
lines changed

2 files changed

+135
-12
lines changed

crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,9 @@ def g(obj: Y):
366366
reveal_type(obj) # revealed: list[int | str]
367367
```
368368

369-
## Generic types
369+
## Generic implicit type aliases
370+
371+
### Functionality
370372

371373
Implicit type aliases can also be generic:
372374

@@ -528,6 +530,36 @@ class Derived2(GenericBaseAlias[int]):
528530
pass
529531
```
530532

533+
### Imported aliases
534+
535+
Generic implicit type aliases can be imported from other modules and specialized:
536+
537+
`my_types.py`:
538+
539+
```py
540+
from typing_extensions import TypeVar
541+
542+
T = TypeVar("T")
543+
544+
MyList = list[T]
545+
```
546+
547+
`main.py`:
548+
549+
```py
550+
from my_types import MyList
551+
import my_types as mt
552+
553+
def _(
554+
list_of_ints1: MyList[int],
555+
list_of_ints2: mt.MyList[int],
556+
):
557+
reveal_type(list_of_ints1) # revealed: list[int]
558+
reveal_type(list_of_ints2) # revealed: list[int]
559+
```
560+
561+
### Error cases
562+
531563
A generic alias that is already fully specialized cannot be specialized again:
532564

533565
```py
@@ -542,6 +574,14 @@ Specializing a generic implicit type alias with an incorrect number of type argu
542574
in an error:
543575

544576
```py
577+
from typing_extensions import TypeVar
578+
579+
T = TypeVar("T")
580+
U = TypeVar("U")
581+
582+
MyList = list[T]
583+
MyDict = dict[T, U]
584+
545585
def _(
546586
# error: [too-many-positional-arguments] "Too many positional arguments: expected 1, got 2"
547587
list_too_many_args: MyList[int, str],
@@ -563,12 +603,79 @@ def this_does_not_work() -> TypeOf[IntOrStr]:
563603
raise NotImplementedError()
564604

565605
def _(
566-
# error: [invalid-type-form] "Cannot specialize a non-name node in a type expression"
606+
# error: [invalid-type-form] "Only name- and attribute expressions can be specialized in type expressions"
567607
specialized: this_does_not_work()[int],
568608
):
569609
reveal_type(specialized) # revealed: Unknown
570610
```
571611

612+
### Multiple definitions
613+
614+
#### Shadowed definitions
615+
616+
When a generic type alias shadows a definition from an outer scope, the inner definition is used:
617+
618+
```py
619+
from typing_extensions import TypeVar
620+
621+
T = TypeVar("T")
622+
623+
MyAlias = list[T]
624+
625+
def outer():
626+
MyAlias = set[T]
627+
628+
def _(x: MyAlias[int]):
629+
reveal_type(x) # revealed: set[int]
630+
```
631+
632+
#### Statically known conditions
633+
634+
```py
635+
from typing_extensions import TypeVar
636+
637+
T = TypeVar("T")
638+
639+
if True:
640+
MyAlias1 = list[T]
641+
else:
642+
MyAlias1 = set[T]
643+
644+
if False:
645+
MyAlias2 = list[T]
646+
else:
647+
MyAlias2 = set[T]
648+
649+
def _(
650+
x1: MyAlias1[int],
651+
x2: MyAlias2[int],
652+
):
653+
reveal_type(x1) # revealed: list[int]
654+
reveal_type(x2) # revealed: set[int]
655+
```
656+
657+
#### Statically unknown conditions
658+
659+
If several definitions are visible, we emit an error:
660+
661+
```py
662+
from typing_extensions import TypeVar
663+
664+
T = TypeVar("T")
665+
666+
def flag() -> bool:
667+
return True
668+
669+
if flag():
670+
MyAlias = list[T]
671+
else:
672+
MyAlias = set[T]
673+
674+
# error: [invalid-type-form] "Invalid subscript of object of type `<class 'list[T@MyAlias]'> | <class 'set[T@MyAlias]'>` in type expression"
675+
def _(x: MyAlias[int]):
676+
reveal_type(x) # revealed: Unknown
677+
```
678+
572679
## `Literal`s
573680

574681
We also support `typing.Literal` in implicit type aliases.

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

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use crate::types::{
1515
KnownInstanceType, LintDiagnosticGuard, Parameter, Parameters, SpecialFormType, SubclassOfType,
1616
Type, TypeAliasType, TypeContext, TypeIsType, TypeMapping, UnionBuilder, UnionType, todo_type,
1717
};
18-
use crate::{FxOrderSet, ResolvedDefinition, definitions_for_name};
18+
use crate::{FxOrderSet, ResolvedDefinition, definitions_for_attribute, definitions_for_name};
1919

2020
/// Type expressions
2121
impl<'db> TypeInferenceBuilder<'db, '_> {
@@ -723,18 +723,34 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
723723
) -> Type<'db> {
724724
let db = self.db();
725725

726-
let Some(value) = subscript.value.as_name_expr() else {
727-
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
728-
builder.into_diagnostic("Cannot specialize a non-name node in a type expression");
726+
let definitions = match &*subscript.value {
727+
ast::Expr::Name(id) => {
728+
// TODO: This is an expensive call to an API that was never meant to be called from
729+
// type inference. We plan to rework how `in_type_expression` works in the future.
730+
// This new approach will make this call unnecessary, so for now, we accept the hit
731+
// in performance.
732+
definitions_for_name(self.db(), self.file(), id)
733+
}
734+
ast::Expr::Attribute(attribute) => {
735+
// TODO: See above
736+
definitions_for_attribute(self.db(), self.file(), attribute)
737+
}
738+
_ => {
739+
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
740+
builder.into_diagnostic(
741+
"Only name- and attribute expressions can be specialized in type expressions",
742+
);
743+
}
744+
return Type::unknown();
729745
}
730-
return Type::unknown();
731746
};
732747

733-
// TODO: This is an expensive call to an API that was never meant to be called from
734-
// type inference. We plan to rework how `in_type_expression` works in the future.
735-
// This new approach will make this call unnecessary, so for now, we accept the hit
736-
// in performance.
737-
let definitions = definitions_for_name(self.db(), self.file(), value);
748+
// TODO: If an implicit type alias is defined multiple times, we arbitrarily pick the
749+
// first definition here. Instead, we should do proper name resolution to find the
750+
// definition that is actually being referenced. Similar to the comments above, this
751+
// should soon be addressed by a rework of how `in_type_expression` works. In the
752+
// meantime, we seem to be doing okay in practice (see "Multiple definitions" tests in
753+
// `implicit_type_aliases.md`).
738754
let Some(type_alias_definition) =
739755
definitions.iter().find_map(ResolvedDefinition::definition)
740756
else {

0 commit comments

Comments
 (0)