Skip to content

Commit b670bf4

Browse files
committed
ban double specialization
1 parent 3a20aea commit b670bf4

File tree

5 files changed

+72
-8
lines changed

5 files changed

+72
-8
lines changed

crates/ty_python_semantic/resources/mdtest/generics/pep695/aliases.md

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,26 @@ def _(b: B[int]):
8080
type IntOrStr = int | str
8181

8282
# error: [non-subscriptable] "Cannot subscript non-generic type alias"
83-
def _(c: IntOrStr[int]): ...
83+
def _(c: IntOrStr[int]):
84+
reveal_type(c) # revealed: Unknown
8485

8586
type ListOfInts = list[int]
8687

87-
# error: [non-subscriptable] "Cannot subscript non-generic type alias"
88-
def _(l: ListOfInts[int]): ...
88+
# error: [non-subscriptable] "Cannot subscript non-generic type alias: `list[int]` is already specialized"
89+
def _(l: ListOfInts[int]):
90+
reveal_type(l) # revealed: Unknown
91+
92+
type List[T] = list[T]
93+
94+
# error: [non-subscriptable] "Cannot subscript non-generic type alias: double specialization is not allowed"
95+
def _(l: List[int][int]):
96+
reveal_type(l) # revealed: Unknown
97+
98+
# error: [non-subscriptable] "Cannot subscript non-generic type alias: `<class 'list[T@DoubleSpecialization]'>` is already specialized"
99+
type DoubleSpecialization[T] = list[T][T]
100+
101+
def _(d: DoubleSpecialization[int]):
102+
reveal_type(d) # revealed: Unknown
89103
```
90104

91105
If the type variable has an upper bound, the specialized type must satisfy that bound:

crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,12 +653,30 @@ def g(obj: Y[bool, range]):
653653

654654
A generic alias that is already fully specialized cannot be specialized again:
655655

656+
```toml
657+
[environment]
658+
python-version = "3.12"
659+
```
660+
656661
```py
657662
ListOfInts = list[int]
658663

659664
# error: [non-subscriptable] "Cannot subscript non-generic type alias: `<class 'list[int]'>` is already specialized"
660665
def _(doubly_specialized: ListOfInts[int]):
661666
reveal_type(doubly_specialized) # revealed: Unknown
667+
668+
type ListOfInts2 = list[int]
669+
# error: [non-subscriptable] "Cannot subscript non-generic type alias: `list[int]` is already specialized"
670+
DoublySpecialized = ListOfInts2[int]
671+
672+
def _(doubly_specialized: DoublySpecialized):
673+
reveal_type(doubly_specialized) # revealed: Unknown
674+
675+
# error: [non-subscriptable] "Cannot subscript non-generic type alias: `<class 'list[int]'>` is already specialized"
676+
List = list[int][int]
677+
678+
def _(doubly_specialized: List):
679+
reveal_type(doubly_specialized) # revealed: Unknown
662680
```
663681

664682
Specializing a generic implicit type alias with an incorrect number of type arguments also results

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12069,9 +12069,19 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
1206912069
Type::KnownInstance(KnownInstanceType::TypeAliasType(TypeAliasType::PEP695(alias))),
1207012070
_,
1207112071
) if alias.generic_context(db).is_none() => {
12072+
debug_assert!(alias.specialization(db).is_none());
1207212073
if let Some(builder) = self.context.report_lint(&NON_SUBSCRIPTABLE, subscript) {
12073-
builder
12074-
.into_diagnostic(format_args!("Cannot subscript non-generic type alias"));
12074+
let value_type = alias.raw_value_type(db);
12075+
if value_type.is_generic_nominal_instance() {
12076+
builder.into_diagnostic(format_args!(
12077+
"Cannot subscript non-generic type alias: `{}` is already specialized",
12078+
value_type.display(db),
12079+
));
12080+
} else {
12081+
builder.into_diagnostic(format_args!(
12082+
"Cannot subscript non-generic type alias"
12083+
));
12084+
}
1207512085
}
1207612086

1207712087
Some(Type::unknown())

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

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,16 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
914914
Type::unknown()
915915
}
916916
KnownInstanceType::TypeAliasType(type_alias @ TypeAliasType::PEP695(_)) => {
917+
if type_alias.specialization(self.db()).is_some() {
918+
if let Some(builder) =
919+
self.context.report_lint(&NON_SUBSCRIPTABLE, subscript)
920+
{
921+
builder.into_diagnostic(format_args!(
922+
"Cannot subscript non-generic type alias: double specialization is not allowed",
923+
));
924+
}
925+
return Type::unknown();
926+
}
917927
match type_alias.generic_context(self.db()) {
918928
Some(generic_context) => {
919929
let specialized_type_alias = self
@@ -938,9 +948,17 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
938948
if let Some(builder) =
939949
self.context.report_lint(&NON_SUBSCRIPTABLE, subscript)
940950
{
941-
builder.into_diagnostic(format_args!(
942-
"Cannot subscript non-generic type alias"
943-
));
951+
let value_type = type_alias.raw_value_type(self.db());
952+
if value_type.is_generic_nominal_instance() {
953+
builder.into_diagnostic(format_args!(
954+
"Cannot subscript non-generic type alias: `{}` is already specialized",
955+
value_type.display(self.db()),
956+
));
957+
} else {
958+
builder.into_diagnostic(format_args!(
959+
"Cannot subscript non-generic type alias"
960+
));
961+
}
944962
}
945963

946964
Type::unknown()

crates/ty_python_semantic/src/types/instance.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ impl<'db> Type<'db> {
9393
matches!(self, Type::NominalInstance(_))
9494
}
9595

96+
pub(crate) const fn is_generic_nominal_instance(self) -> bool {
97+
matches!(self, Type::NominalInstance(instance_type) if matches!(instance_type.0, NominalInstanceInner::NonTuple(class) if class.is_generic()))
98+
}
99+
96100
pub(crate) const fn as_nominal_instance(self) -> Option<NominalInstanceType<'db>> {
97101
match self {
98102
Type::NominalInstance(instance_type) => Some(instance_type),

0 commit comments

Comments
 (0)