mirror of https://github.com/astral-sh/ruff
[ty] improve bad specialization results & error messages (#21840)
## Summary
This PR includes the following changes:
* When attempting to specialize a non-generic type (or a type that is
already specialized), the result is `Unknown`. Also, the error message
is improved.
* When an implicit type alias is incorrectly specialized, the result is
`Unknown`. Also, the error message is improved.
* When only some of the type alias bounds and constraints are not
satisfied, not all substitutions are `Unknown`.
* Double specialization is prohibited. e.g. `G[int][int]`
Furthermore, after applying this PR, the fuzzing tests for seeds 1052
and 4419, which panic in main, now pass.
This is because the false recursions on type variables have been
removed.
```python
# name_2[0] => Unknown
class name_1[name_2: name_2[0]]:
def name_4(name_3: name_2, /):
if name_3:
pass
# (name_5 if unique_name_0 else name_1)[0] => Unknown
def name_4[name_5: (name_5 if unique_name_0 else name_1)[0], **name_1](): ...
```
## Test Plan
New corpus test
mdtest files updated
This commit is contained in:
parent
ddb7645e9d
commit
5e42926eee
|
|
@ -0,0 +1 @@
|
||||||
|
def _[T: (T if cond else U)[0], U](): pass
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
class _[T: (0, T[0])]:
|
||||||
|
def _(x: T):
|
||||||
|
if x:
|
||||||
|
pass
|
||||||
|
|
@ -283,7 +283,7 @@ reveal_type(OnlyParamSpec[...]().attr) # revealed: (...) -> None
|
||||||
def func(c: Callable[P2, None]):
|
def func(c: Callable[P2, None]):
|
||||||
reveal_type(OnlyParamSpec[P2]().attr) # revealed: (**P2@func) -> None
|
reveal_type(OnlyParamSpec[P2]().attr) # revealed: (**P2@func) -> None
|
||||||
|
|
||||||
# TODO: error: paramspec is unbound
|
# error: [invalid-type-arguments] "ParamSpec `P2` is unbound"
|
||||||
reveal_type(OnlyParamSpec[P2]().attr) # revealed: (...) -> None
|
reveal_type(OnlyParamSpec[P2]().attr) # revealed: (...) -> None
|
||||||
|
|
||||||
# error: [invalid-type-arguments] "No type argument provided for required type variable `P1` of class `OnlyParamSpec`"
|
# error: [invalid-type-arguments] "No type argument provided for required type variable `P1` of class `OnlyParamSpec`"
|
||||||
|
|
@ -327,15 +327,14 @@ reveal_type(TypeVarAndParamSpec[int, [int, str]]().attr) # revealed: (int, str,
|
||||||
reveal_type(TypeVarAndParamSpec[int, [str]]().attr) # revealed: (str, /) -> int
|
reveal_type(TypeVarAndParamSpec[int, [str]]().attr) # revealed: (str, /) -> int
|
||||||
reveal_type(TypeVarAndParamSpec[int, ...]().attr) # revealed: (...) -> int
|
reveal_type(TypeVarAndParamSpec[int, ...]().attr) # revealed: (...) -> int
|
||||||
|
|
||||||
# TODO: We could still specialize for `T1` as the type is valid which would reveal `(...) -> int`
|
# error: [invalid-type-arguments] "ParamSpec `P2` is unbound"
|
||||||
# TODO: error: paramspec is unbound
|
reveal_type(TypeVarAndParamSpec[int, P2]().attr) # revealed: (...) -> int
|
||||||
reveal_type(TypeVarAndParamSpec[int, P2]().attr) # revealed: (...) -> Unknown
|
# error: [invalid-type-arguments] "Type argument for `ParamSpec` must be either a list of types, `ParamSpec`, `Concatenate`, or `...`"
|
||||||
|
reveal_type(TypeVarAndParamSpec[int, int]().attr) # revealed: (...) -> int
|
||||||
# error: [invalid-type-arguments] "Type argument for `ParamSpec` must be"
|
# error: [invalid-type-arguments] "Type argument for `ParamSpec` must be"
|
||||||
reveal_type(TypeVarAndParamSpec[int, int]().attr) # revealed: (...) -> Unknown
|
reveal_type(TypeVarAndParamSpec[int, ()]().attr) # revealed: (...) -> int
|
||||||
# error: [invalid-type-arguments] "Type argument for `ParamSpec` must be"
|
# error: [invalid-type-arguments] "Type argument for `ParamSpec` must be"
|
||||||
reveal_type(TypeVarAndParamSpec[int, ()]().attr) # revealed: (...) -> Unknown
|
reveal_type(TypeVarAndParamSpec[int, (int, str)]().attr) # revealed: (...) -> int
|
||||||
# error: [invalid-type-arguments] "Type argument for `ParamSpec` must be"
|
|
||||||
reveal_type(TypeVarAndParamSpec[int, (int, str)]().attr) # revealed: (...) -> Unknown
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Nor can they be omitted when there are more than one `ParamSpec`s.
|
Nor can they be omitted when there are more than one `ParamSpec`s.
|
||||||
|
|
|
||||||
|
|
@ -68,13 +68,91 @@ reveal_type(C[int, int]) # revealed: <type alias 'C[Unknown]'>
|
||||||
And non-generic types cannot be specialized:
|
And non-generic types cannot be specialized:
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
from typing import TypeVar, Protocol, TypedDict
|
||||||
|
|
||||||
type B = ...
|
type B = ...
|
||||||
|
|
||||||
# error: [non-subscriptable] "Cannot subscript non-generic type alias"
|
# error: [non-subscriptable] "Cannot subscript non-generic type alias"
|
||||||
reveal_type(B[int]) # revealed: Unknown
|
reveal_type(B[int]) # revealed: Unknown
|
||||||
|
|
||||||
# error: [non-subscriptable] "Cannot subscript non-generic type alias"
|
# error: [non-subscriptable] "Cannot subscript non-generic type alias"
|
||||||
def _(b: B[int]): ...
|
def _(b: B[int]):
|
||||||
|
reveal_type(b) # revealed: Unknown
|
||||||
|
|
||||||
|
type IntOrStr = int | str
|
||||||
|
|
||||||
|
# error: [non-subscriptable] "Cannot subscript non-generic type alias"
|
||||||
|
def _(c: IntOrStr[int]):
|
||||||
|
reveal_type(c) # revealed: Unknown
|
||||||
|
|
||||||
|
type ListOfInts = list[int]
|
||||||
|
|
||||||
|
# error: [non-subscriptable] "Cannot subscript non-generic type alias: `list[int]` is already specialized"
|
||||||
|
def _(l: ListOfInts[int]):
|
||||||
|
reveal_type(l) # revealed: Unknown
|
||||||
|
|
||||||
|
type List[T] = list[T]
|
||||||
|
|
||||||
|
# error: [non-subscriptable] "Cannot subscript non-generic type alias: Double specialization is not allowed"
|
||||||
|
def _(l: List[int][int]):
|
||||||
|
reveal_type(l) # revealed: Unknown
|
||||||
|
|
||||||
|
# error: [non-subscriptable] "Cannot subscript non-generic type: `<class 'list[T@DoubleSpecialization]'>` is already specialized"
|
||||||
|
type DoubleSpecialization[T] = list[T][T]
|
||||||
|
|
||||||
|
def _(d: DoubleSpecialization[int]):
|
||||||
|
reveal_type(d) # revealed: Unknown
|
||||||
|
|
||||||
|
type Tuple = tuple[int, str]
|
||||||
|
|
||||||
|
# error: [non-subscriptable] "Cannot subscript non-generic type alias: `tuple[int, str]` is already specialized"
|
||||||
|
def _(doubly_specialized: Tuple[int]):
|
||||||
|
reveal_type(doubly_specialized) # revealed: Unknown
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
class LegacyProto(Protocol[T]):
|
||||||
|
pass
|
||||||
|
|
||||||
|
type LegacyProtoInt = LegacyProto[int]
|
||||||
|
|
||||||
|
# error: [non-subscriptable] "Cannot subscript non-generic type alias: `LegacyProto[int]` is already specialized"
|
||||||
|
def _(x: LegacyProtoInt[int]):
|
||||||
|
reveal_type(x) # revealed: Unknown
|
||||||
|
|
||||||
|
class Proto[T](Protocol):
|
||||||
|
pass
|
||||||
|
|
||||||
|
type ProtoInt = Proto[int]
|
||||||
|
|
||||||
|
# error: [non-subscriptable] "Cannot subscript non-generic type alias: `Proto[int]` is already specialized"
|
||||||
|
def _(x: ProtoInt[int]):
|
||||||
|
reveal_type(x) # revealed: Unknown
|
||||||
|
|
||||||
|
# TODO: TypedDict is just a function object at runtime, we should emit an error
|
||||||
|
class LegacyDict(TypedDict[T]):
|
||||||
|
x: T
|
||||||
|
|
||||||
|
type LegacyDictInt = LegacyDict[int]
|
||||||
|
|
||||||
|
# error: [non-subscriptable] "Cannot subscript non-generic type alias"
|
||||||
|
def _(x: LegacyDictInt[int]):
|
||||||
|
reveal_type(x) # revealed: Unknown
|
||||||
|
|
||||||
|
class Dict[T](TypedDict):
|
||||||
|
x: T
|
||||||
|
|
||||||
|
type DictInt = Dict[int]
|
||||||
|
|
||||||
|
# error: [non-subscriptable] "Cannot subscript non-generic type alias: `Dict` is already specialized"
|
||||||
|
def _(x: DictInt[int]):
|
||||||
|
reveal_type(x) # revealed: Unknown
|
||||||
|
|
||||||
|
type Union = list[str] | list[int]
|
||||||
|
|
||||||
|
# error: [non-subscriptable] "Cannot subscript non-generic type alias: `list[str] | list[int]` is already specialized"
|
||||||
|
def _(x: Union[int]):
|
||||||
|
reveal_type(x) # revealed: Unknown
|
||||||
```
|
```
|
||||||
|
|
||||||
If the type variable has an upper bound, the specialized type must satisfy that bound:
|
If the type variable has an upper bound, the specialized type must satisfy that bound:
|
||||||
|
|
@ -98,6 +176,15 @@ reveal_type(BoundedByUnion[int]) # revealed: <type alias 'BoundedByUnion[int]'>
|
||||||
reveal_type(BoundedByUnion[IntSubclass]) # revealed: <type alias 'BoundedByUnion[IntSubclass]'>
|
reveal_type(BoundedByUnion[IntSubclass]) # revealed: <type alias 'BoundedByUnion[IntSubclass]'>
|
||||||
reveal_type(BoundedByUnion[str]) # revealed: <type alias 'BoundedByUnion[str]'>
|
reveal_type(BoundedByUnion[str]) # revealed: <type alias 'BoundedByUnion[str]'>
|
||||||
reveal_type(BoundedByUnion[int | str]) # revealed: <type alias 'BoundedByUnion[int | str]'>
|
reveal_type(BoundedByUnion[int | str]) # revealed: <type alias 'BoundedByUnion[int | str]'>
|
||||||
|
|
||||||
|
type TupleOfIntAndStr[T: int, U: str] = tuple[T, U]
|
||||||
|
|
||||||
|
def _(x: TupleOfIntAndStr[int, str]):
|
||||||
|
reveal_type(x) # revealed: tuple[int, str]
|
||||||
|
|
||||||
|
# error: [invalid-type-arguments] "Type `int` is not assignable to upper bound `str` of type variable `U@TupleOfIntAndStr`"
|
||||||
|
def _(x: TupleOfIntAndStr[int, int]):
|
||||||
|
reveal_type(x) # revealed: tuple[int, Unknown]
|
||||||
```
|
```
|
||||||
|
|
||||||
If the type variable is constrained, the specialized type must satisfy those constraints:
|
If the type variable is constrained, the specialized type must satisfy those constraints:
|
||||||
|
|
@ -119,6 +206,15 @@ reveal_type(Constrained[int | str]) # revealed: <type alias 'Constrained[int |
|
||||||
|
|
||||||
# error: [invalid-type-arguments] "Type `object` does not satisfy constraints `int`, `str` of type variable `T@Constrained`"
|
# error: [invalid-type-arguments] "Type `object` does not satisfy constraints `int`, `str` of type variable `T@Constrained`"
|
||||||
reveal_type(Constrained[object]) # revealed: <type alias 'Constrained[Unknown]'>
|
reveal_type(Constrained[object]) # revealed: <type alias 'Constrained[Unknown]'>
|
||||||
|
|
||||||
|
type TupleOfIntOrStr[T: (int, str), U: (int, str)] = tuple[T, U]
|
||||||
|
|
||||||
|
def _(x: TupleOfIntOrStr[int, str]):
|
||||||
|
reveal_type(x) # revealed: tuple[int, str]
|
||||||
|
|
||||||
|
# error: [invalid-type-arguments] "Type `object` does not satisfy constraints `int`, `str` of type variable `U@TupleOfIntOrStr`"
|
||||||
|
def _(x: TupleOfIntOrStr[int, object]):
|
||||||
|
reveal_type(x) # revealed: tuple[int, Unknown]
|
||||||
```
|
```
|
||||||
|
|
||||||
If the type variable has a default, it can be omitted:
|
If the type variable has a default, it can be omitted:
|
||||||
|
|
|
||||||
|
|
@ -237,7 +237,7 @@ def func[**P2](c: Callable[P2, None]):
|
||||||
|
|
||||||
P2 = ParamSpec("P2")
|
P2 = ParamSpec("P2")
|
||||||
|
|
||||||
# TODO: error: paramspec is unbound
|
# error: [invalid-type-arguments] "ParamSpec `P2` is unbound"
|
||||||
reveal_type(OnlyParamSpec[P2]().attr) # revealed: (...) -> None
|
reveal_type(OnlyParamSpec[P2]().attr) # revealed: (...) -> None
|
||||||
|
|
||||||
# error: [invalid-type-arguments] "No type argument provided for required type variable `P1` of class `OnlyParamSpec`"
|
# error: [invalid-type-arguments] "No type argument provided for required type variable `P1` of class `OnlyParamSpec`"
|
||||||
|
|
@ -281,14 +281,14 @@ reveal_type(TypeVarAndParamSpec[int, [int, str]]().attr) # revealed: (int, str,
|
||||||
reveal_type(TypeVarAndParamSpec[int, [str]]().attr) # revealed: (str, /) -> int
|
reveal_type(TypeVarAndParamSpec[int, [str]]().attr) # revealed: (str, /) -> int
|
||||||
reveal_type(TypeVarAndParamSpec[int, ...]().attr) # revealed: (...) -> int
|
reveal_type(TypeVarAndParamSpec[int, ...]().attr) # revealed: (...) -> int
|
||||||
|
|
||||||
# TODO: error: paramspec is unbound
|
# error: [invalid-type-arguments] "ParamSpec `P2` is unbound"
|
||||||
reveal_type(TypeVarAndParamSpec[int, P2]().attr) # revealed: (...) -> Unknown
|
reveal_type(TypeVarAndParamSpec[int, P2]().attr) # revealed: (...) -> int
|
||||||
# error: [invalid-type-arguments] "Type argument for `ParamSpec` must be"
|
# error: [invalid-type-arguments] "Type argument for `ParamSpec` must be"
|
||||||
reveal_type(TypeVarAndParamSpec[int, int]().attr) # revealed: (...) -> Unknown
|
reveal_type(TypeVarAndParamSpec[int, int]().attr) # revealed: (...) -> int
|
||||||
# error: [invalid-type-arguments] "Type argument for `ParamSpec` must be"
|
# error: [invalid-type-arguments] "Type argument for `ParamSpec` must be"
|
||||||
reveal_type(TypeVarAndParamSpec[int, ()]().attr) # revealed: (...) -> Unknown
|
reveal_type(TypeVarAndParamSpec[int, ()]().attr) # revealed: (...) -> int
|
||||||
# error: [invalid-type-arguments] "Type argument for `ParamSpec` must be"
|
# error: [invalid-type-arguments] "Type argument for `ParamSpec` must be"
|
||||||
reveal_type(TypeVarAndParamSpec[int, (int, str)]().attr) # revealed: (...) -> Unknown
|
reveal_type(TypeVarAndParamSpec[int, (int, str)]().attr) # revealed: (...) -> int
|
||||||
```
|
```
|
||||||
|
|
||||||
Nor can they be omitted when there are more than one `ParamSpec`.
|
Nor can they be omitted when there are more than one `ParamSpec`.
|
||||||
|
|
|
||||||
|
|
@ -653,13 +653,92 @@ def g(obj: Y[bool, range]):
|
||||||
|
|
||||||
A generic alias that is already fully specialized cannot be specialized again:
|
A generic alias that is already fully specialized cannot be specialized again:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.12"
|
||||||
|
```
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
from typing import Protocol, TypeVar, TypedDict
|
||||||
|
|
||||||
ListOfInts = list[int]
|
ListOfInts = list[int]
|
||||||
|
|
||||||
# error: [invalid-type-arguments] "Too many type arguments: expected 0, got 1"
|
# error: [non-subscriptable] "Cannot subscript non-generic type: `<class 'list[int]'>` is already specialized"
|
||||||
def _(doubly_specialized: ListOfInts[int]):
|
def _(doubly_specialized: ListOfInts[int]):
|
||||||
# TODO: This should ideally be `list[Unknown]` or `Unknown`
|
reveal_type(doubly_specialized) # revealed: Unknown
|
||||||
reveal_type(doubly_specialized) # revealed: list[int]
|
|
||||||
|
type ListOfInts2 = list[int]
|
||||||
|
# error: [non-subscriptable] "Cannot subscript non-generic type alias: `list[int]` is already specialized"
|
||||||
|
DoublySpecialized = ListOfInts2[int]
|
||||||
|
|
||||||
|
def _(doubly_specialized: DoublySpecialized):
|
||||||
|
reveal_type(doubly_specialized) # revealed: Unknown
|
||||||
|
|
||||||
|
# error: [non-subscriptable] "Cannot subscript non-generic type: `<class 'list[int]'>` is already specialized"
|
||||||
|
List = list[int][int]
|
||||||
|
|
||||||
|
def _(doubly_specialized: List):
|
||||||
|
reveal_type(doubly_specialized) # revealed: Unknown
|
||||||
|
|
||||||
|
Tuple = tuple[int, str]
|
||||||
|
|
||||||
|
# error: [non-subscriptable] "Cannot subscript non-generic type: `<class 'tuple[int, str]'>` is already specialized"
|
||||||
|
def _(doubly_specialized: Tuple[int]):
|
||||||
|
reveal_type(doubly_specialized) # revealed: Unknown
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
class LegacyProto(Protocol[T]):
|
||||||
|
pass
|
||||||
|
|
||||||
|
LegacyProtoInt = LegacyProto[int]
|
||||||
|
|
||||||
|
# error: [non-subscriptable] "Cannot subscript non-generic type: `<class 'LegacyProto[int]'>` is already specialized"
|
||||||
|
def _(doubly_specialized: LegacyProtoInt[int]):
|
||||||
|
reveal_type(doubly_specialized) # revealed: Unknown
|
||||||
|
|
||||||
|
class Proto[T](Protocol):
|
||||||
|
pass
|
||||||
|
|
||||||
|
ProtoInt = Proto[int]
|
||||||
|
|
||||||
|
# error: [non-subscriptable] "Cannot subscript non-generic type: `<class 'Proto[int]'>` is already specialized"
|
||||||
|
def _(doubly_specialized: ProtoInt[int]):
|
||||||
|
reveal_type(doubly_specialized) # revealed: Unknown
|
||||||
|
|
||||||
|
# TODO: TypedDict is just a function object at runtime, we should emit an error
|
||||||
|
class LegacyDict(TypedDict[T]):
|
||||||
|
x: T
|
||||||
|
|
||||||
|
# TODO: should be a `non-subscriptable` error
|
||||||
|
LegacyDictInt = LegacyDict[int]
|
||||||
|
|
||||||
|
# TODO: should be a `non-subscriptable` error
|
||||||
|
def _(doubly_specialized: LegacyDictInt[int]):
|
||||||
|
# TODO: should be `Unknown`
|
||||||
|
reveal_type(doubly_specialized) # revealed: @Todo(Inference of subscript on special form)
|
||||||
|
|
||||||
|
class Dict[T](TypedDict):
|
||||||
|
x: T
|
||||||
|
|
||||||
|
DictInt = Dict[int]
|
||||||
|
|
||||||
|
# error: [non-subscriptable] "Cannot subscript non-generic type: `<class 'Dict[int]'>` is already specialized"
|
||||||
|
def _(doubly_specialized: DictInt[int]):
|
||||||
|
reveal_type(doubly_specialized) # revealed: Unknown
|
||||||
|
|
||||||
|
Union = list[str] | list[int]
|
||||||
|
|
||||||
|
# error: [non-subscriptable] "Cannot subscript non-generic type: `<types.UnionType special form 'list[str] | list[int]'>` is already specialized"
|
||||||
|
def _(doubly_specialized: Union[int]):
|
||||||
|
reveal_type(doubly_specialized) # revealed: Unknown
|
||||||
|
|
||||||
|
type MyListAlias[T] = list[T]
|
||||||
|
MyListOfInts = MyListAlias[int]
|
||||||
|
|
||||||
|
# error: [non-subscriptable] "Cannot subscript non-generic type alias: Double specialization is not allowed"
|
||||||
|
def _(doubly_specialized: MyListOfInts[int]):
|
||||||
|
reveal_type(doubly_specialized) # revealed: Unknown
|
||||||
```
|
```
|
||||||
|
|
||||||
Specializing a generic implicit type alias with an incorrect number of type arguments also results
|
Specializing a generic implicit type alias with an incorrect number of type arguments also results
|
||||||
|
|
@ -695,23 +774,21 @@ def this_does_not_work() -> TypeOf[IntOrStr]:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def _(
|
def _(
|
||||||
# TODO: Better error message (of kind `invalid-type-form`)?
|
# error: [non-subscriptable] "Cannot subscript non-generic type"
|
||||||
# error: [invalid-type-arguments] "Too many type arguments: expected 0, got 1"
|
|
||||||
specialized: this_does_not_work()[int],
|
specialized: this_does_not_work()[int],
|
||||||
):
|
):
|
||||||
reveal_type(specialized) # revealed: int | str
|
reveal_type(specialized) # revealed: Unknown
|
||||||
```
|
```
|
||||||
|
|
||||||
Similarly, if you try to specialize a union type without a binding context, we emit an error:
|
Similarly, if you try to specialize a union type without a binding context, we emit an error:
|
||||||
|
|
||||||
```py
|
```py
|
||||||
# TODO: Better error message (of kind `invalid-type-form`)?
|
# error: [non-subscriptable] "Cannot subscript non-generic type"
|
||||||
# error: [invalid-type-arguments] "Too many type arguments: expected 0, got 1"
|
|
||||||
x: (list[T] | set[T])[int]
|
x: (list[T] | set[T])[int]
|
||||||
|
|
||||||
def _():
|
def _():
|
||||||
# TODO: `list[Unknown] | set[Unknown]` might be better
|
# TODO: `list[Unknown] | set[Unknown]` might be better
|
||||||
reveal_type(x) # revealed: list[typing.TypeVar] | set[typing.TypeVar]
|
reveal_type(x) # revealed: Unknown
|
||||||
```
|
```
|
||||||
|
|
||||||
### Multiple definitions
|
### Multiple definitions
|
||||||
|
|
|
||||||
|
|
@ -989,6 +989,41 @@ impl<'db> Type<'db> {
|
||||||
matches!(self, Type::GenericAlias(_))
|
matches!(self, Type::GenericAlias(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns whether the definition of this type is generic
|
||||||
|
/// (this is different from whether this type *is* a generic type; a type that is already fully specialized is not a generic type).
|
||||||
|
pub(crate) fn is_definition_generic(self, db: &'db dyn Db) -> bool {
|
||||||
|
match self {
|
||||||
|
Type::Union(union) => union
|
||||||
|
.elements(db)
|
||||||
|
.iter()
|
||||||
|
.any(|ty| ty.is_definition_generic(db)),
|
||||||
|
Type::Intersection(intersection) => {
|
||||||
|
intersection
|
||||||
|
.positive(db)
|
||||||
|
.iter()
|
||||||
|
.any(|ty| ty.is_definition_generic(db))
|
||||||
|
|| intersection
|
||||||
|
.negative(db)
|
||||||
|
.iter()
|
||||||
|
.any(|ty| ty.is_definition_generic(db))
|
||||||
|
}
|
||||||
|
Type::NominalInstance(instance_type) => instance_type.is_definition_generic(),
|
||||||
|
Type::ProtocolInstance(protocol) => {
|
||||||
|
matches!(protocol.inner, Protocol::FromClass(class) if class.is_generic())
|
||||||
|
}
|
||||||
|
Type::TypedDict(typed_dict) => typed_dict
|
||||||
|
.defining_class()
|
||||||
|
.is_some_and(ClassType::is_generic),
|
||||||
|
Type::Dynamic(dynamic) => {
|
||||||
|
matches!(dynamic, DynamicType::UnknownGeneric(_))
|
||||||
|
}
|
||||||
|
// Due to inheritance rules, enums cannot be generic.
|
||||||
|
Type::EnumLiteral(_) => false,
|
||||||
|
// Once generic NewType is officially specified, handle it.
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const fn is_dynamic(&self) -> bool {
|
const fn is_dynamic(&self) -> bool {
|
||||||
matches!(self, Type::Dynamic(_))
|
matches!(self, Type::Dynamic(_))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3541,10 +3541,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
return Ok(param_type);
|
return Ok(param_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
Type::KnownInstance(known_instance)
|
Type::KnownInstance(known_instance @ KnownInstanceType::TypeVar(typevar))
|
||||||
if known_instance.class(self.db()) == KnownClass::ParamSpec =>
|
if known_instance.class(self.db()) == KnownClass::ParamSpec =>
|
||||||
{
|
{
|
||||||
// TODO: Emit diagnostic: "ParamSpec "P" is unbound"
|
if let Some(diagnostic_builder) =
|
||||||
|
self.context.report_lint(&INVALID_TYPE_ARGUMENTS, expr)
|
||||||
|
{
|
||||||
|
diagnostic_builder.into_diagnostic(format_args!(
|
||||||
|
"ParamSpec `{}` is unbound",
|
||||||
|
typevar.name(self.db())
|
||||||
|
));
|
||||||
|
}
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -11636,6 +11643,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
generic_context: GenericContext<'db>,
|
generic_context: GenericContext<'db>,
|
||||||
specialize: impl FnOnce(&[Option<Type<'db>>]) -> Type<'db>,
|
specialize: impl FnOnce(&[Option<Type<'db>>]) -> Type<'db>,
|
||||||
) -> Type<'db> {
|
) -> Type<'db> {
|
||||||
|
enum ExplicitSpecializationError {
|
||||||
|
InvalidParamSpec,
|
||||||
|
UnsatisfiedBound,
|
||||||
|
UnsatisfiedConstraints,
|
||||||
|
/// These two errors override the errors above, causing all specializations to be `Unknown`.
|
||||||
|
MissingTypeVars,
|
||||||
|
TooManyArguments,
|
||||||
|
/// This error overrides the errors above, causing the type itself to be `Unknown`.
|
||||||
|
NonGeneric,
|
||||||
|
}
|
||||||
|
|
||||||
fn add_typevar_definition<'db>(
|
fn add_typevar_definition<'db>(
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
diagnostic: &mut Diagnostic,
|
diagnostic: &mut Diagnostic,
|
||||||
|
|
@ -11688,7 +11706,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut has_error = false;
|
let mut error: Option<ExplicitSpecializationError> = None;
|
||||||
|
|
||||||
for (index, item) in typevars.zip_longest(type_arguments.iter()).enumerate() {
|
for (index, item) in typevars.zip_longest(type_arguments.iter()).enumerate() {
|
||||||
match item {
|
match item {
|
||||||
|
|
@ -11704,8 +11722,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
) {
|
) {
|
||||||
Ok(paramspec_value) => paramspec_value,
|
Ok(paramspec_value) => paramspec_value,
|
||||||
Err(()) => {
|
Err(()) => {
|
||||||
has_error = true;
|
error = Some(ExplicitSpecializationError::InvalidParamSpec);
|
||||||
Type::unknown()
|
Type::paramspec_value_callable(db, Parameters::unknown())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -11737,8 +11755,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
));
|
));
|
||||||
add_typevar_definition(db, &mut diagnostic, typevar);
|
add_typevar_definition(db, &mut diagnostic, typevar);
|
||||||
}
|
}
|
||||||
has_error = true;
|
error = Some(ExplicitSpecializationError::UnsatisfiedBound);
|
||||||
continue;
|
specialization_types.push(Some(Type::unknown()));
|
||||||
|
} else {
|
||||||
|
specialization_types.push(Some(provided_type));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
|
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
|
||||||
|
|
@ -11771,14 +11791,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
));
|
));
|
||||||
add_typevar_definition(db, &mut diagnostic, typevar);
|
add_typevar_definition(db, &mut diagnostic, typevar);
|
||||||
}
|
}
|
||||||
has_error = true;
|
error = Some(ExplicitSpecializationError::UnsatisfiedConstraints);
|
||||||
continue;
|
specialization_types.push(Some(Type::unknown()));
|
||||||
|
} else {
|
||||||
|
specialization_types.push(Some(provided_type));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {}
|
None => {
|
||||||
|
specialization_types.push(Some(provided_type));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
specialization_types.push(Some(provided_type));
|
|
||||||
}
|
}
|
||||||
EitherOrBoth::Left(typevar) => {
|
EitherOrBoth::Left(typevar) => {
|
||||||
if typevar.default_type(db).is_none() {
|
if typevar.default_type(db).is_none() {
|
||||||
|
|
@ -11813,33 +11835,57 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
has_error = true;
|
error = Some(ExplicitSpecializationError::MissingTypeVars);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(first_excess_type_argument_index) = first_excess_type_argument_index {
|
if let Some(first_excess_type_argument_index) = first_excess_type_argument_index {
|
||||||
let node = get_node(first_excess_type_argument_index);
|
if typevars_len == 0 {
|
||||||
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_ARGUMENTS, node) {
|
// Type parameter list cannot be empty, so if we reach here, `value_ty` is not a generic type.
|
||||||
let description = CallableDescription::new(db, value_ty);
|
if let Some(builder) = self
|
||||||
builder.into_diagnostic(format_args!(
|
.context
|
||||||
"Too many type arguments{}: expected {}, got {}",
|
.report_lint(&NON_SUBSCRIPTABLE, &*subscript.value)
|
||||||
if let Some(CallableDescription { kind, name }) = description {
|
{
|
||||||
format!(" to {kind} `{name}`")
|
let mut diagnostic =
|
||||||
} else {
|
builder.into_diagnostic("Cannot subscript non-generic type");
|
||||||
String::new()
|
if match value_ty {
|
||||||
},
|
Type::GenericAlias(_) => true,
|
||||||
if typevar_with_defaults == 0 {
|
Type::KnownInstance(KnownInstanceType::UnionType(union)) => union
|
||||||
format!("{typevars_len}")
|
.value_expression_types(db)
|
||||||
} else {
|
.is_ok_and(|mut tys| tys.any(|ty| ty.is_generic_alias())),
|
||||||
format!(
|
_ => false,
|
||||||
"between {} and {}",
|
} {
|
||||||
typevars_len - typevar_with_defaults,
|
diagnostic.set_primary_message(format_args!(
|
||||||
typevars_len
|
"`{}` is already specialized",
|
||||||
)
|
value_ty.display(db)
|
||||||
},
|
));
|
||||||
type_arguments.len(),
|
}
|
||||||
));
|
}
|
||||||
|
error = Some(ExplicitSpecializationError::NonGeneric);
|
||||||
|
} else {
|
||||||
|
let node = get_node(first_excess_type_argument_index);
|
||||||
|
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_ARGUMENTS, node) {
|
||||||
|
let description = CallableDescription::new(db, value_ty);
|
||||||
|
builder.into_diagnostic(format_args!(
|
||||||
|
"Too many type arguments{}: expected {}, got {}",
|
||||||
|
if let Some(CallableDescription { kind, name }) = description {
|
||||||
|
format!(" to {kind} `{name}`")
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
},
|
||||||
|
if typevar_with_defaults == 0 {
|
||||||
|
format!("{typevars_len}")
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"between {} and {}",
|
||||||
|
typevars_len - typevar_with_defaults,
|
||||||
|
typevars_len
|
||||||
|
)
|
||||||
|
},
|
||||||
|
type_arguments.len(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
error = Some(ExplicitSpecializationError::TooManyArguments);
|
||||||
}
|
}
|
||||||
has_error = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if store_inferred_type_arguments {
|
if store_inferred_type_arguments {
|
||||||
|
|
@ -11849,21 +11895,31 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if has_error {
|
match error {
|
||||||
let unknowns = generic_context
|
Some(ExplicitSpecializationError::NonGeneric) => Type::unknown(),
|
||||||
.variables(self.db())
|
Some(
|
||||||
.map(|typevar| {
|
ExplicitSpecializationError::MissingTypeVars
|
||||||
Some(if typevar.is_paramspec(db) {
|
| ExplicitSpecializationError::TooManyArguments,
|
||||||
Type::paramspec_value_callable(db, Parameters::unknown())
|
) => {
|
||||||
} else {
|
let unknowns = generic_context
|
||||||
Type::unknown()
|
.variables(self.db())
|
||||||
|
.map(|typevar| {
|
||||||
|
Some(if typevar.is_paramspec(db) {
|
||||||
|
Type::paramspec_value_callable(db, Parameters::unknown())
|
||||||
|
} else {
|
||||||
|
Type::unknown()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
.collect::<Vec<_>>();
|
||||||
.collect::<Vec<_>>();
|
specialize(&unknowns)
|
||||||
return specialize(&unknowns);
|
}
|
||||||
|
Some(
|
||||||
|
ExplicitSpecializationError::UnsatisfiedBound
|
||||||
|
| ExplicitSpecializationError::UnsatisfiedConstraints
|
||||||
|
| ExplicitSpecializationError::InvalidParamSpec,
|
||||||
|
)
|
||||||
|
| None => specialize(&specialization_types),
|
||||||
}
|
}
|
||||||
|
|
||||||
specialize(&specialization_types)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infer_subscript_expression_types(
|
fn infer_subscript_expression_types(
|
||||||
|
|
@ -12044,9 +12100,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
Type::KnownInstance(KnownInstanceType::TypeAliasType(TypeAliasType::PEP695(alias))),
|
Type::KnownInstance(KnownInstanceType::TypeAliasType(TypeAliasType::PEP695(alias))),
|
||||||
_,
|
_,
|
||||||
) if alias.generic_context(db).is_none() => {
|
) if alias.generic_context(db).is_none() => {
|
||||||
|
debug_assert!(alias.specialization(db).is_none());
|
||||||
if let Some(builder) = self.context.report_lint(&NON_SUBSCRIPTABLE, subscript) {
|
if let Some(builder) = self.context.report_lint(&NON_SUBSCRIPTABLE, subscript) {
|
||||||
builder
|
let value_type = alias.raw_value_type(db);
|
||||||
.into_diagnostic(format_args!("Cannot subscript non-generic type alias"));
|
let mut diagnostic =
|
||||||
|
builder.into_diagnostic("Cannot subscript non-generic type alias");
|
||||||
|
if value_type.is_definition_generic(db) {
|
||||||
|
diagnostic.set_primary_message(format_args!(
|
||||||
|
"`{}` is already specialized",
|
||||||
|
value_type.display(db)
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(Type::unknown())
|
Some(Type::unknown())
|
||||||
|
|
|
||||||
|
|
@ -919,6 +919,16 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
Type::unknown()
|
Type::unknown()
|
||||||
}
|
}
|
||||||
KnownInstanceType::TypeAliasType(type_alias @ TypeAliasType::PEP695(_)) => {
|
KnownInstanceType::TypeAliasType(type_alias @ TypeAliasType::PEP695(_)) => {
|
||||||
|
if type_alias.specialization(self.db()).is_some() {
|
||||||
|
if let Some(builder) =
|
||||||
|
self.context.report_lint(&NON_SUBSCRIPTABLE, subscript)
|
||||||
|
{
|
||||||
|
let mut diagnostic =
|
||||||
|
builder.into_diagnostic("Cannot subscript non-generic type alias");
|
||||||
|
diagnostic.set_primary_message("Double specialization is not allowed");
|
||||||
|
}
|
||||||
|
return Type::unknown();
|
||||||
|
}
|
||||||
match type_alias.generic_context(self.db()) {
|
match type_alias.generic_context(self.db()) {
|
||||||
Some(generic_context) => {
|
Some(generic_context) => {
|
||||||
let specialized_type_alias = self
|
let specialized_type_alias = self
|
||||||
|
|
@ -943,9 +953,15 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
if let Some(builder) =
|
if let Some(builder) =
|
||||||
self.context.report_lint(&NON_SUBSCRIPTABLE, subscript)
|
self.context.report_lint(&NON_SUBSCRIPTABLE, subscript)
|
||||||
{
|
{
|
||||||
builder.into_diagnostic(format_args!(
|
let value_type = type_alias.raw_value_type(self.db());
|
||||||
"Cannot subscript non-generic type alias"
|
let mut diagnostic = builder
|
||||||
));
|
.into_diagnostic("Cannot subscript non-generic type alias");
|
||||||
|
if value_type.is_definition_generic(self.db()) {
|
||||||
|
diagnostic.set_primary_message(format_args!(
|
||||||
|
"`{}` is already specialized",
|
||||||
|
value_type.display(self.db()),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Type::unknown()
|
Type::unknown()
|
||||||
|
|
|
||||||
|
|
@ -304,6 +304,14 @@ impl<'db> NominalInstanceType<'db> {
|
||||||
matches!(self.0, NominalInstanceInner::Object)
|
matches!(self.0, NominalInstanceInner::Object)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn is_definition_generic(self) -> bool {
|
||||||
|
match self.0 {
|
||||||
|
NominalInstanceInner::NonTuple(class) => class.is_generic(),
|
||||||
|
NominalInstanceInner::ExactTuple(_) => true,
|
||||||
|
NominalInstanceInner::Object => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// If this type is an *exact* tuple type (*not* a subclass of `tuple`), returns the
|
/// If this type is an *exact* tuple type (*not* a subclass of `tuple`), returns the
|
||||||
/// tuple spec.
|
/// tuple spec.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue