[ty] improve bad specialization results & error messages

This commit is contained in:
Shunsuke Shibayama 2025-12-08 14:55:58 +09:00
parent 857fd4f683
commit 3a20aeaede
6 changed files with 145 additions and 66 deletions

View File

@ -3,3 +3,5 @@ def name_1[name_0: name_0](name_2: name_0):
pass pass
except name_2: except name_2:
pass pass
def _[T: (T if cond else U)[0], U](): pass

View File

@ -250,7 +250,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-form] "ParamSpec `P2` is unbound"
reveal_type(OnlyParamSpec[P2]().attr) # revealed: (...) -> None reveal_type(OnlyParamSpec[P2]().attr) # revealed: (...) -> None
``` ```
@ -273,11 +273,10 @@ 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-form] "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 `...`" # 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: (...) -> Unknown reveal_type(TypeVarAndParamSpec[int, int]().attr) # revealed: (...) -> int
``` ```
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.

View File

@ -74,7 +74,18 @@ type B = ...
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]): ...
type ListOfInts = list[int]
# error: [non-subscriptable] "Cannot subscript non-generic type alias"
def _(l: ListOfInts[int]): ...
``` ```
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 +109,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 +139,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:

View File

@ -236,7 +236,7 @@ def func[**P2](c: Callable[P2, None]):
P2 = ParamSpec("P2") P2 = ParamSpec("P2")
# TODO: error: paramspec is unbound # error: [invalid-type-form] "ParamSpec `P2` is unbound"
reveal_type(OnlyParamSpec[P2]().attr) # revealed: (...) -> None reveal_type(OnlyParamSpec[P2]().attr) # revealed: (...) -> None
``` ```
@ -259,10 +259,10 @@ 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-form] "ParamSpec `P2` is unbound"
reveal_type(TypeVarAndParamSpec[int, P2]().attr) # revealed: (...) -> Unknown reveal_type(TypeVarAndParamSpec[int, P2]().attr) # revealed: (...) -> int
# error: [invalid-type-arguments] # error: [invalid-type-arguments]
reveal_type(TypeVarAndParamSpec[int, int]().attr) # revealed: (...) -> Unknown reveal_type(TypeVarAndParamSpec[int, int]().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`.

View File

@ -656,10 +656,9 @@ A generic alias that is already fully specialized cannot be specialized again:
```py ```py
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 alias: `<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]
``` ```
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 +694,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 alias"
# 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 alias"
# 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

View File

@ -3527,10 +3527,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_FORM, expr)
{
diagnostic_builder.into_diagnostic(format_args!(
"ParamSpec `{}` is unbound",
typevar.name(self.db())
));
}
return Err(()); return Err(());
} }
@ -11609,6 +11616,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,
@ -11661,7 +11679,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 {
@ -11677,8 +11695,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 {
@ -11710,8 +11728,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)) => {
@ -11744,15 +11764,17 @@ 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 {
}
None => {}
}
specialization_types.push(Some(provided_type)); specialization_types.push(Some(provided_type));
} }
}
None => {
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() {
// This is an error case, so no need to push into the specialization types. // This is an error case, so no need to push into the specialization types.
@ -11786,10 +11808,29 @@ 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 {
if typevars_len == 0 {
// Type parameter list cannot be empty, so if we reach here, `value_ty` is not a generic type.
if let Some(builder) = self
.context
.report_lint(&NON_SUBSCRIPTABLE, &*subscript.value)
{
if value_ty.is_generic_alias() {
builder.into_diagnostic(format_args!(
"Cannot subscript non-generic type alias: `{}` is already specialized",
value_ty.display(db),
));
} else {
builder.into_diagnostic(format_args!(
"Cannot subscript non-generic type alias"
));
}
}
error = Some(ExplicitSpecializationError::NonGeneric);
} else {
let node = get_node(first_excess_type_argument_index); let node = get_node(first_excess_type_argument_index);
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_ARGUMENTS, node) { if let Some(builder) = self.context.report_lint(&INVALID_TYPE_ARGUMENTS, node) {
let description = CallableDescription::new(db, value_ty); let description = CallableDescription::new(db, value_ty);
@ -11812,7 +11853,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
type_arguments.len(), type_arguments.len(),
)); ));
} }
has_error = true; error = Some(ExplicitSpecializationError::TooManyArguments);
}
} }
if store_inferred_type_arguments { if store_inferred_type_arguments {
@ -11822,7 +11864,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
); );
} }
if has_error { match error {
Some(ExplicitSpecializationError::NonGeneric) => Type::unknown(),
Some(
ExplicitSpecializationError::MissingTypeVars
| ExplicitSpecializationError::TooManyArguments,
) => {
let unknowns = generic_context let unknowns = generic_context
.variables(self.db()) .variables(self.db())
.map(|typevar| { .map(|typevar| {
@ -11833,10 +11880,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}) })
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
return specialize(&unknowns); 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(