mirror of https://github.com/astral-sh/ruff
[ty] improve bad specialization results & error messages
This commit is contained in:
parent
857fd4f683
commit
3a20aeaede
|
|
@ -3,3 +3,5 @@ def name_1[name_0: name_0](name_2: name_0):
|
|||
pass
|
||||
except name_2:
|
||||
pass
|
||||
|
||||
def _[T: (T if cond else U)[0], U](): pass
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ reveal_type(OnlyParamSpec[...]().attr) # revealed: (...) -> None
|
|||
def func(c: Callable[P2, 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
|
||||
```
|
||||
|
||||
|
|
@ -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, ...]().attr) # revealed: (...) -> int
|
||||
|
||||
# TODO: We could still specialize for `T1` as the type is valid which would reveal `(...) -> int`
|
||||
# TODO: error: paramspec is unbound
|
||||
reveal_type(TypeVarAndParamSpec[int, P2]().attr) # revealed: (...) -> Unknown
|
||||
# error: [invalid-type-form] "ParamSpec `P2` is unbound"
|
||||
reveal_type(TypeVarAndParamSpec[int, P2]().attr) # revealed: (...) -> int
|
||||
# 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.
|
||||
|
|
|
|||
|
|
@ -74,7 +74,18 @@ type B = ...
|
|||
reveal_type(B[int]) # revealed: Unknown
|
||||
|
||||
# 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:
|
||||
|
|
@ -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[str]) # revealed: <type alias 'BoundedByUnion[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:
|
||||
|
|
@ -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`"
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -236,7 +236,7 @@ def func[**P2](c: Callable[P2, None]):
|
|||
|
||||
P2 = ParamSpec("P2")
|
||||
|
||||
# TODO: error: paramspec is unbound
|
||||
# error: [invalid-type-form] "ParamSpec `P2` is unbound"
|
||||
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, ...]().attr) # revealed: (...) -> int
|
||||
|
||||
# TODO: error: paramspec is unbound
|
||||
reveal_type(TypeVarAndParamSpec[int, P2]().attr) # revealed: (...) -> Unknown
|
||||
# error: [invalid-type-form] "ParamSpec `P2` is unbound"
|
||||
reveal_type(TypeVarAndParamSpec[int, P2]().attr) # revealed: (...) -> int
|
||||
# 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`.
|
||||
|
|
|
|||
|
|
@ -656,10 +656,9 @@ A generic alias that is already fully specialized cannot be specialized again:
|
|||
```py
|
||||
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]):
|
||||
# TODO: This should ideally be `list[Unknown]` or `Unknown`
|
||||
reveal_type(doubly_specialized) # revealed: list[int]
|
||||
reveal_type(doubly_specialized) # revealed: Unknown
|
||||
```
|
||||
|
||||
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()
|
||||
|
||||
def _(
|
||||
# TODO: Better error message (of kind `invalid-type-form`)?
|
||||
# error: [invalid-type-arguments] "Too many type arguments: expected 0, got 1"
|
||||
# error: [non-subscriptable] "Cannot subscript non-generic type alias"
|
||||
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:
|
||||
|
||||
```py
|
||||
# TODO: Better error message (of kind `invalid-type-form`)?
|
||||
# error: [invalid-type-arguments] "Too many type arguments: expected 0, got 1"
|
||||
# error: [non-subscriptable] "Cannot subscript non-generic type alias"
|
||||
x: (list[T] | set[T])[int]
|
||||
|
||||
def _():
|
||||
# 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
|
||||
|
|
|
|||
|
|
@ -3527,10 +3527,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
return Ok(param_type);
|
||||
}
|
||||
|
||||
Type::KnownInstance(known_instance)
|
||||
Type::KnownInstance(known_instance @ KnownInstanceType::TypeVar(typevar))
|
||||
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(());
|
||||
}
|
||||
|
||||
|
|
@ -11609,6 +11616,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
generic_context: GenericContext<'db>,
|
||||
specialize: impl FnOnce(&[Option<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>(
|
||||
db: &'db dyn Db,
|
||||
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() {
|
||||
match item {
|
||||
|
|
@ -11677,8 +11695,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
) {
|
||||
Ok(paramspec_value) => paramspec_value,
|
||||
Err(()) => {
|
||||
has_error = true;
|
||||
Type::unknown()
|
||||
error = Some(ExplicitSpecializationError::InvalidParamSpec);
|
||||
Type::paramspec_value_callable(db, Parameters::unknown())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -11710,8 +11728,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
));
|
||||
add_typevar_definition(db, &mut diagnostic, typevar);
|
||||
}
|
||||
has_error = true;
|
||||
continue;
|
||||
error = Some(ExplicitSpecializationError::UnsatisfiedBound);
|
||||
specialization_types.push(Some(Type::unknown()));
|
||||
} else {
|
||||
specialization_types.push(Some(provided_type));
|
||||
}
|
||||
}
|
||||
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
|
||||
|
|
@ -11744,14 +11764,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
));
|
||||
add_typevar_definition(db, &mut diagnostic, typevar);
|
||||
}
|
||||
has_error = true;
|
||||
continue;
|
||||
error = Some(ExplicitSpecializationError::UnsatisfiedConstraints);
|
||||
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) => {
|
||||
if typevar.default_type(db).is_none() {
|
||||
|
|
@ -11786,33 +11808,53 @@ 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 {
|
||||
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}`")
|
||||
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 {
|
||||
String::new()
|
||||
},
|
||||
if typevar_with_defaults == 0 {
|
||||
format!("{typevars_len}")
|
||||
} else {
|
||||
format!(
|
||||
"between {} and {}",
|
||||
typevars_len - typevar_with_defaults,
|
||||
typevars_len
|
||||
)
|
||||
},
|
||||
type_arguments.len(),
|
||||
));
|
||||
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);
|
||||
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 {
|
||||
|
|
@ -11822,21 +11864,31 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
);
|
||||
}
|
||||
|
||||
if has_error {
|
||||
let unknowns = generic_context
|
||||
.variables(self.db())
|
||||
.map(|typevar| {
|
||||
Some(if typevar.is_paramspec(db) {
|
||||
Type::paramspec_value_callable(db, Parameters::unknown())
|
||||
} else {
|
||||
Type::unknown()
|
||||
match error {
|
||||
Some(ExplicitSpecializationError::NonGeneric) => Type::unknown(),
|
||||
Some(
|
||||
ExplicitSpecializationError::MissingTypeVars
|
||||
| ExplicitSpecializationError::TooManyArguments,
|
||||
) => {
|
||||
let unknowns = generic_context
|
||||
.variables(self.db())
|
||||
.map(|typevar| {
|
||||
Some(if typevar.is_paramspec(db) {
|
||||
Type::paramspec_value_callable(db, Parameters::unknown())
|
||||
} else {
|
||||
Type::unknown()
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
return specialize(&unknowns);
|
||||
.collect::<Vec<_>>();
|
||||
specialize(&unknowns)
|
||||
}
|
||||
Some(
|
||||
ExplicitSpecializationError::UnsatisfiedBound
|
||||
| ExplicitSpecializationError::UnsatisfiedConstraints
|
||||
| ExplicitSpecializationError::InvalidParamSpec,
|
||||
)
|
||||
| None => specialize(&specialization_types),
|
||||
}
|
||||
|
||||
specialize(&specialization_types)
|
||||
}
|
||||
|
||||
fn infer_subscript_expression_types(
|
||||
|
|
|
|||
Loading…
Reference in New Issue