Skip to content

Commit 3a20aea

Browse files
committed
[ty] improve bad specialization results & error messages
1 parent 857fd4f commit 3a20aea

File tree

6 files changed

+145
-66
lines changed

6 files changed

+145
-66
lines changed

crates/ty_python_semantic/resources/corpus/cyclic_pep695_typevars.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ def name_1[name_0: name_0](name_2: name_0):
33
pass
44
except name_2:
55
pass
6+
7+
def _[T: (T if cond else U)[0], U](): pass

crates/ty_python_semantic/resources/mdtest/generics/legacy/paramspec.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ reveal_type(OnlyParamSpec[...]().attr) # revealed: (...) -> None
250250
def func(c: Callable[P2, None]):
251251
reveal_type(OnlyParamSpec[P2]().attr) # revealed: (**P2@func) -> None
252252

253-
# TODO: error: paramspec is unbound
253+
# error: [invalid-type-form] "ParamSpec `P2` is unbound"
254254
reveal_type(OnlyParamSpec[P2]().attr) # revealed: (...) -> None
255255
```
256256

@@ -273,11 +273,10 @@ reveal_type(TypeVarAndParamSpec[int, [int, str]]().attr) # revealed: (int, str,
273273
reveal_type(TypeVarAndParamSpec[int, [str]]().attr) # revealed: (str, /) -> int
274274
reveal_type(TypeVarAndParamSpec[int, ...]().attr) # revealed: (...) -> int
275275

276-
# TODO: We could still specialize for `T1` as the type is valid which would reveal `(...) -> int`
277-
# TODO: error: paramspec is unbound
278-
reveal_type(TypeVarAndParamSpec[int, P2]().attr) # revealed: (...) -> Unknown
276+
# error: [invalid-type-form] "ParamSpec `P2` is unbound"
277+
reveal_type(TypeVarAndParamSpec[int, P2]().attr) # revealed: (...) -> int
279278
# error: [invalid-type-arguments] "Type argument for `ParamSpec` must be either a list of types, `ParamSpec`, `Concatenate`, or `...`"
280-
reveal_type(TypeVarAndParamSpec[int, int]().attr) # revealed: (...) -> Unknown
279+
reveal_type(TypeVarAndParamSpec[int, int]().attr) # revealed: (...) -> int
281280
```
282281

283282
Nor can they be omitted when there are more than one `ParamSpec`s.

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

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,18 @@ type B = ...
7474
reveal_type(B[int]) # revealed: Unknown
7575

7676
# error: [non-subscriptable] "Cannot subscript non-generic type alias"
77-
def _(b: B[int]): ...
77+
def _(b: B[int]):
78+
reveal_type(b) # revealed: Unknown
79+
80+
type IntOrStr = int | str
81+
82+
# error: [non-subscriptable] "Cannot subscript non-generic type alias"
83+
def _(c: IntOrStr[int]): ...
84+
85+
type ListOfInts = list[int]
86+
87+
# error: [non-subscriptable] "Cannot subscript non-generic type alias"
88+
def _(l: ListOfInts[int]): ...
7889
```
7990

8091
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]'>
98109
reveal_type(BoundedByUnion[IntSubclass]) # revealed: <type alias 'BoundedByUnion[IntSubclass]'>
99110
reveal_type(BoundedByUnion[str]) # revealed: <type alias 'BoundedByUnion[str]'>
100111
reveal_type(BoundedByUnion[int | str]) # revealed: <type alias 'BoundedByUnion[int | str]'>
112+
113+
type TupleOfIntAndStr[T: int, U: str] = tuple[T, U]
114+
115+
def _(x: TupleOfIntAndStr[int, str]):
116+
reveal_type(x) # revealed: tuple[int, str]
117+
118+
# error: [invalid-type-arguments] "Type `int` is not assignable to upper bound `str` of type variable `U@TupleOfIntAndStr`"
119+
def _(x: TupleOfIntAndStr[int, int]):
120+
reveal_type(x) # revealed: tuple[int, Unknown]
101121
```
102122

103123
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 |
119139

120140
# error: [invalid-type-arguments] "Type `object` does not satisfy constraints `int`, `str` of type variable `T@Constrained`"
121141
reveal_type(Constrained[object]) # revealed: <type alias 'Constrained[Unknown]'>
142+
143+
type TupleOfIntOrStr[T: (int, str), U: (int, str)] = tuple[T, U]
144+
145+
def _(x: TupleOfIntOrStr[int, str]):
146+
reveal_type(x) # revealed: tuple[int, str]
147+
148+
# error: [invalid-type-arguments] "Type `object` does not satisfy constraints `int`, `str` of type variable `U@TupleOfIntOrStr`"
149+
def _(x: TupleOfIntOrStr[int, object]):
150+
reveal_type(x) # revealed: tuple[int, Unknown]
122151
```
123152

124153
If the type variable has a default, it can be omitted:

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ def func[**P2](c: Callable[P2, None]):
236236

237237
P2 = ParamSpec("P2")
238238

239-
# TODO: error: paramspec is unbound
239+
# error: [invalid-type-form] "ParamSpec `P2` is unbound"
240240
reveal_type(OnlyParamSpec[P2]().attr) # revealed: (...) -> None
241241
```
242242

@@ -259,10 +259,10 @@ reveal_type(TypeVarAndParamSpec[int, [int, str]]().attr) # revealed: (int, str,
259259
reveal_type(TypeVarAndParamSpec[int, [str]]().attr) # revealed: (str, /) -> int
260260
reveal_type(TypeVarAndParamSpec[int, ...]().attr) # revealed: (...) -> int
261261

262-
# TODO: error: paramspec is unbound
263-
reveal_type(TypeVarAndParamSpec[int, P2]().attr) # revealed: (...) -> Unknown
262+
# error: [invalid-type-form] "ParamSpec `P2` is unbound"
263+
reveal_type(TypeVarAndParamSpec[int, P2]().attr) # revealed: (...) -> int
264264
# error: [invalid-type-arguments]
265-
reveal_type(TypeVarAndParamSpec[int, int]().attr) # revealed: (...) -> Unknown
265+
reveal_type(TypeVarAndParamSpec[int, int]().attr) # revealed: (...) -> int
266266
```
267267

268268
Nor can they be omitted when there are more than one `ParamSpec`.

crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -656,10 +656,9 @@ A generic alias that is already fully specialized cannot be specialized again:
656656
```py
657657
ListOfInts = list[int]
658658

659-
# error: [invalid-type-arguments] "Too many type arguments: expected 0, got 1"
659+
# error: [non-subscriptable] "Cannot subscript non-generic type alias: `<class 'list[int]'>` is already specialized"
660660
def _(doubly_specialized: ListOfInts[int]):
661-
# TODO: This should ideally be `list[Unknown]` or `Unknown`
662-
reveal_type(doubly_specialized) # revealed: list[int]
661+
reveal_type(doubly_specialized) # revealed: Unknown
663662
```
664663

665664
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]:
695694
raise NotImplementedError()
696695

697696
def _(
698-
# TODO: Better error message (of kind `invalid-type-form`)?
699-
# error: [invalid-type-arguments] "Too many type arguments: expected 0, got 1"
697+
# error: [non-subscriptable] "Cannot subscript non-generic type alias"
700698
specialized: this_does_not_work()[int],
701699
):
702-
reveal_type(specialized) # revealed: int | str
700+
reveal_type(specialized) # revealed: Unknown
703701
```
704702

705703
Similarly, if you try to specialize a union type without a binding context, we emit an error:
706704

707705
```py
708-
# TODO: Better error message (of kind `invalid-type-form`)?
709-
# error: [invalid-type-arguments] "Too many type arguments: expected 0, got 1"
706+
# error: [non-subscriptable] "Cannot subscript non-generic type alias"
710707
x: (list[T] | set[T])[int]
711708

712709
def _():
713710
# TODO: `list[Unknown] | set[Unknown]` might be better
714-
reveal_type(x) # revealed: list[typing.TypeVar] | set[typing.TypeVar]
711+
reveal_type(x) # revealed: Unknown
715712
```
716713

717714
### Multiple definitions

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

Lines changed: 99 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3527,10 +3527,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
35273527
return Ok(param_type);
35283528
}
35293529

3530-
Type::KnownInstance(known_instance)
3530+
Type::KnownInstance(known_instance @ KnownInstanceType::TypeVar(typevar))
35313531
if known_instance.class(self.db()) == KnownClass::ParamSpec =>
35323532
{
3533-
// TODO: Emit diagnostic: "ParamSpec "P" is unbound"
3533+
if let Some(diagnostic_builder) =
3534+
self.context.report_lint(&INVALID_TYPE_FORM, expr)
3535+
{
3536+
diagnostic_builder.into_diagnostic(format_args!(
3537+
"ParamSpec `{}` is unbound",
3538+
typevar.name(self.db())
3539+
));
3540+
}
35343541
return Err(());
35353542
}
35363543

@@ -11609,6 +11616,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
1160911616
generic_context: GenericContext<'db>,
1161011617
specialize: impl FnOnce(&[Option<Type<'db>>]) -> Type<'db>,
1161111618
) -> Type<'db> {
11619+
enum ExplicitSpecializationError {
11620+
InvalidParamSpec,
11621+
UnsatisfiedBound,
11622+
UnsatisfiedConstraints,
11623+
/// These two errors override the errors above, causing all specializations to be `Unknown`.
11624+
MissingTypeVars,
11625+
TooManyArguments,
11626+
/// This error overrides the errors above, causing the type itself to be `Unknown`.
11627+
NonGeneric,
11628+
}
11629+
1161211630
fn add_typevar_definition<'db>(
1161311631
db: &'db dyn Db,
1161411632
diagnostic: &mut Diagnostic,
@@ -11661,7 +11679,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
1166111679
}
1166211680
};
1166311681

11664-
let mut has_error = false;
11682+
let mut error: Option<ExplicitSpecializationError> = None;
1166511683

1166611684
for (index, item) in typevars.zip_longest(type_arguments.iter()).enumerate() {
1166711685
match item {
@@ -11677,8 +11695,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
1167711695
) {
1167811696
Ok(paramspec_value) => paramspec_value,
1167911697
Err(()) => {
11680-
has_error = true;
11681-
Type::unknown()
11698+
error = Some(ExplicitSpecializationError::InvalidParamSpec);
11699+
Type::paramspec_value_callable(db, Parameters::unknown())
1168211700
}
1168311701
}
1168411702
} else {
@@ -11710,8 +11728,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
1171011728
));
1171111729
add_typevar_definition(db, &mut diagnostic, typevar);
1171211730
}
11713-
has_error = true;
11714-
continue;
11731+
error = Some(ExplicitSpecializationError::UnsatisfiedBound);
11732+
specialization_types.push(Some(Type::unknown()));
11733+
} else {
11734+
specialization_types.push(Some(provided_type));
1171511735
}
1171611736
}
1171711737
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
@@ -11744,14 +11764,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
1174411764
));
1174511765
add_typevar_definition(db, &mut diagnostic, typevar);
1174611766
}
11747-
has_error = true;
11748-
continue;
11767+
error = Some(ExplicitSpecializationError::UnsatisfiedConstraints);
11768+
specialization_types.push(Some(Type::unknown()));
11769+
} else {
11770+
specialization_types.push(Some(provided_type));
1174911771
}
1175011772
}
11751-
None => {}
11773+
None => {
11774+
specialization_types.push(Some(provided_type));
11775+
}
1175211776
}
11753-
11754-
specialization_types.push(Some(provided_type));
1175511777
}
1175611778
EitherOrBoth::Left(typevar) => {
1175711779
if typevar.default_type(db).is_none() {
@@ -11786,33 +11808,53 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
1178611808
}
1178711809
));
1178811810
}
11789-
has_error = true;
11811+
error = Some(ExplicitSpecializationError::MissingTypeVars);
1179011812
}
1179111813

1179211814
if let Some(first_excess_type_argument_index) = first_excess_type_argument_index {
11793-
let node = get_node(first_excess_type_argument_index);
11794-
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_ARGUMENTS, node) {
11795-
let description = CallableDescription::new(db, value_ty);
11796-
builder.into_diagnostic(format_args!(
11797-
"Too many type arguments{}: expected {}, got {}",
11798-
if let Some(CallableDescription { kind, name }) = description {
11799-
format!(" to {kind} `{name}`")
11800-
} else {
11801-
String::new()
11802-
},
11803-
if typevar_with_defaults == 0 {
11804-
format!("{typevars_len}")
11815+
if typevars_len == 0 {
11816+
// Type parameter list cannot be empty, so if we reach here, `value_ty` is not a generic type.
11817+
if let Some(builder) = self
11818+
.context
11819+
.report_lint(&NON_SUBSCRIPTABLE, &*subscript.value)
11820+
{
11821+
if value_ty.is_generic_alias() {
11822+
builder.into_diagnostic(format_args!(
11823+
"Cannot subscript non-generic type alias: `{}` is already specialized",
11824+
value_ty.display(db),
11825+
));
1180511826
} else {
11806-
format!(
11807-
"between {} and {}",
11808-
typevars_len - typevar_with_defaults,
11809-
typevars_len
11810-
)
11811-
},
11812-
type_arguments.len(),
11813-
));
11827+
builder.into_diagnostic(format_args!(
11828+
"Cannot subscript non-generic type alias"
11829+
));
11830+
}
11831+
}
11832+
error = Some(ExplicitSpecializationError::NonGeneric);
11833+
} else {
11834+
let node = get_node(first_excess_type_argument_index);
11835+
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_ARGUMENTS, node) {
11836+
let description = CallableDescription::new(db, value_ty);
11837+
builder.into_diagnostic(format_args!(
11838+
"Too many type arguments{}: expected {}, got {}",
11839+
if let Some(CallableDescription { kind, name }) = description {
11840+
format!(" to {kind} `{name}`")
11841+
} else {
11842+
String::new()
11843+
},
11844+
if typevar_with_defaults == 0 {
11845+
format!("{typevars_len}")
11846+
} else {
11847+
format!(
11848+
"between {} and {}",
11849+
typevars_len - typevar_with_defaults,
11850+
typevars_len
11851+
)
11852+
},
11853+
type_arguments.len(),
11854+
));
11855+
}
11856+
error = Some(ExplicitSpecializationError::TooManyArguments);
1181411857
}
11815-
has_error = true;
1181611858
}
1181711859

1181811860
if store_inferred_type_arguments {
@@ -11822,21 +11864,31 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
1182211864
);
1182311865
}
1182411866

11825-
if has_error {
11826-
let unknowns = generic_context
11827-
.variables(self.db())
11828-
.map(|typevar| {
11829-
Some(if typevar.is_paramspec(db) {
11830-
Type::paramspec_value_callable(db, Parameters::unknown())
11831-
} else {
11832-
Type::unknown()
11867+
match error {
11868+
Some(ExplicitSpecializationError::NonGeneric) => Type::unknown(),
11869+
Some(
11870+
ExplicitSpecializationError::MissingTypeVars
11871+
| ExplicitSpecializationError::TooManyArguments,
11872+
) => {
11873+
let unknowns = generic_context
11874+
.variables(self.db())
11875+
.map(|typevar| {
11876+
Some(if typevar.is_paramspec(db) {
11877+
Type::paramspec_value_callable(db, Parameters::unknown())
11878+
} else {
11879+
Type::unknown()
11880+
})
1183311881
})
11834-
})
11835-
.collect::<Vec<_>>();
11836-
return specialize(&unknowns);
11882+
.collect::<Vec<_>>();
11883+
specialize(&unknowns)
11884+
}
11885+
Some(
11886+
ExplicitSpecializationError::UnsatisfiedBound
11887+
| ExplicitSpecializationError::UnsatisfiedConstraints
11888+
| ExplicitSpecializationError::InvalidParamSpec,
11889+
)
11890+
| None => specialize(&specialization_types),
1183711891
}
11838-
11839-
specialize(&specialization_types)
1184011892
}
1184111893

1184211894
fn infer_subscript_expression_types(

0 commit comments

Comments
 (0)