mirror of https://github.com/astral-sh/ruff
[ty] Show the user where the type variable was defined in `invalid-type-arguments` diagnostics (#21727)
This commit is contained in:
parent
a2096ee2cb
commit
3a11e714c6
|
|
@ -210,6 +210,37 @@ reveal_type(WithDefault[str, str]()) # revealed: WithDefault[str, str]
|
|||
reveal_type(WithDefault[str]()) # revealed: WithDefault[str, int]
|
||||
```
|
||||
|
||||
## Diagnostics for bad specializations
|
||||
|
||||
We show the user where the type variable was defined if a specialization is given that doesn't
|
||||
satisfy the type variable's upper bound or constraints:
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
`library.py`:
|
||||
|
||||
```py
|
||||
from typing import TypeVar, Generic
|
||||
|
||||
T = TypeVar("T", bound=str)
|
||||
U = TypeVar("U", int, bytes)
|
||||
|
||||
class Bounded(Generic[T]):
|
||||
x: T
|
||||
|
||||
class Constrained(Generic[U]):
|
||||
x: U
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
from library import Bounded, Constrained
|
||||
|
||||
x: Bounded[int] # error: [invalid-type-arguments]
|
||||
y: Constrained[str] # error: [invalid-type-arguments]
|
||||
```
|
||||
|
||||
## Inferring generic class parameters
|
||||
|
||||
We can infer the type parameter from a type context:
|
||||
|
|
|
|||
|
|
@ -191,6 +191,32 @@ reveal_type(WithDefault[str, str]()) # revealed: WithDefault[str, str]
|
|||
reveal_type(WithDefault[str]()) # revealed: WithDefault[str, int]
|
||||
```
|
||||
|
||||
## Diagnostics for bad specializations
|
||||
|
||||
We show the user where the type variable was defined if a specialization is given that doesn't
|
||||
satisfy the type variable's upper bound or constraints:
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
`library.py`:
|
||||
|
||||
```py
|
||||
class Bounded[T: str]:
|
||||
x: T
|
||||
|
||||
class Constrained[U: (int, bytes)]:
|
||||
x: U
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
from library import Bounded, Constrained
|
||||
|
||||
x: Bounded[int] # error: [invalid-type-arguments]
|
||||
y: Constrained[str] # error: [invalid-type-arguments]
|
||||
```
|
||||
|
||||
## Inferring generic class parameters
|
||||
|
||||
We can infer the type parameter from a type context:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: classes.md - Generic classes: Legacy syntax - Diagnostics for bad specializations
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## library.py
|
||||
|
||||
```
|
||||
1 | from typing import TypeVar, Generic
|
||||
2 |
|
||||
3 | T = TypeVar("T", bound=str)
|
||||
4 | U = TypeVar("U", int, bytes)
|
||||
5 |
|
||||
6 | class Bounded(Generic[T]):
|
||||
7 | x: T
|
||||
8 |
|
||||
9 | class Constrained(Generic[U]):
|
||||
10 | x: U
|
||||
```
|
||||
|
||||
## main.py
|
||||
|
||||
```
|
||||
1 | from library import Bounded, Constrained
|
||||
2 |
|
||||
3 | x: Bounded[int] # error: [invalid-type-arguments]
|
||||
4 | y: Constrained[str] # error: [invalid-type-arguments]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-type-arguments]: Type `int` is not assignable to upper bound `str` of type variable `T@Bounded`
|
||||
--> src/main.py:3:12
|
||||
|
|
||||
1 | from library import Bounded, Constrained
|
||||
2 |
|
||||
3 | x: Bounded[int] # error: [invalid-type-arguments]
|
||||
| ^^^
|
||||
4 | y: Constrained[str] # error: [invalid-type-arguments]
|
||||
|
|
||||
::: src/library.py:3:1
|
||||
|
|
||||
1 | from typing import TypeVar, Generic
|
||||
2 |
|
||||
3 | T = TypeVar("T", bound=str)
|
||||
| - Type variable defined here
|
||||
4 | U = TypeVar("U", int, bytes)
|
||||
|
|
||||
info: rule `invalid-type-arguments` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-type-arguments]: Type `str` does not satisfy constraints `int`, `bytes` of type variable `U@Constrained`
|
||||
--> src/main.py:4:16
|
||||
|
|
||||
3 | x: Bounded[int] # error: [invalid-type-arguments]
|
||||
4 | y: Constrained[str] # error: [invalid-type-arguments]
|
||||
| ^^^
|
||||
|
|
||||
::: src/library.py:4:1
|
||||
|
|
||||
3 | T = TypeVar("T", bound=str)
|
||||
4 | U = TypeVar("U", int, bytes)
|
||||
| - Type variable defined here
|
||||
5 |
|
||||
6 | class Bounded(Generic[T]):
|
||||
|
|
||||
info: rule `invalid-type-arguments` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: classes.md - Generic classes: PEP 695 syntax - Diagnostics for bad specializations
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## library.py
|
||||
|
||||
```
|
||||
1 | class Bounded[T: str]:
|
||||
2 | x: T
|
||||
3 |
|
||||
4 | class Constrained[U: (int, bytes)]:
|
||||
5 | x: U
|
||||
```
|
||||
|
||||
## main.py
|
||||
|
||||
```
|
||||
1 | from library import Bounded, Constrained
|
||||
2 |
|
||||
3 | x: Bounded[int] # error: [invalid-type-arguments]
|
||||
4 | y: Constrained[str] # error: [invalid-type-arguments]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-type-arguments]: Type `int` is not assignable to upper bound `str` of type variable `T@Bounded`
|
||||
--> src/main.py:3:12
|
||||
|
|
||||
1 | from library import Bounded, Constrained
|
||||
2 |
|
||||
3 | x: Bounded[int] # error: [invalid-type-arguments]
|
||||
| ^^^
|
||||
4 | y: Constrained[str] # error: [invalid-type-arguments]
|
||||
|
|
||||
::: src/library.py:1:15
|
||||
|
|
||||
1 | class Bounded[T: str]:
|
||||
| - Type variable defined here
|
||||
2 | x: T
|
||||
|
|
||||
info: rule `invalid-type-arguments` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-type-arguments]: Type `str` does not satisfy constraints `int`, `bytes` of type variable `U@Constrained`
|
||||
--> src/main.py:4:16
|
||||
|
|
||||
3 | x: Bounded[int] # error: [invalid-type-arguments]
|
||||
4 | y: Constrained[str] # error: [invalid-type-arguments]
|
||||
| ^^^
|
||||
|
|
||||
::: src/library.py:4:19
|
||||
|
|
||||
2 | x: T
|
||||
3 |
|
||||
4 | class Constrained[U: (int, bytes)]:
|
||||
| - Type variable defined here
|
||||
5 | x: U
|
||||
|
|
||||
info: rule `invalid-type-arguments` is enabled by default
|
||||
|
||||
```
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
use std::iter;
|
||||
|
||||
use itertools::{Either, EitherOrBoth, Itertools};
|
||||
use ruff_db::diagnostic::{Annotation, DiagnosticId, Severity};
|
||||
use ruff_db::diagnostic::{Annotation, Diagnostic, DiagnosticId, Severity, Span};
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::parsed::ParsedModuleRef;
|
||||
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
|
||||
use ruff_python_ast::visitor::{Visitor, walk_expr};
|
||||
use ruff_python_ast::{
|
||||
self as ast, AnyNodeRef, ExprContext, HasNodeIndex, NodeIndex, PythonVersion,
|
||||
|
|
@ -102,14 +102,15 @@ use crate::types::typed_dict::{
|
|||
};
|
||||
use crate::types::visitor::any_over_type;
|
||||
use crate::types::{
|
||||
CallDunderError, CallableBinding, CallableType, CallableTypes, ClassLiteral, ClassType,
|
||||
DataclassParams, DynamicType, InternedType, IntersectionBuilder, IntersectionType, KnownClass,
|
||||
KnownInstanceType, LintDiagnosticGuard, MemberLookupPolicy, MetaclassCandidate,
|
||||
PEP695TypeAliasType, ParameterForm, SpecialFormType, SubclassOfType, TrackedConstraintSet,
|
||||
Truthiness, Type, TypeAliasType, TypeAndQualifiers, TypeContext, TypeQualifiers,
|
||||
TypeVarBoundOrConstraints, TypeVarBoundOrConstraintsEvaluation, TypeVarDefaultEvaluation,
|
||||
TypeVarIdentity, TypeVarInstance, TypeVarKind, TypeVarVariance, TypedDictType, UnionBuilder,
|
||||
UnionType, UnionTypeInstance, binding_type, infer_scope_types, overrides, todo_type,
|
||||
BoundTypeVarInstance, CallDunderError, CallableBinding, CallableType, CallableTypes,
|
||||
ClassLiteral, ClassType, DataclassParams, DynamicType, InternedType, IntersectionBuilder,
|
||||
IntersectionType, KnownClass, KnownInstanceType, LintDiagnosticGuard, MemberLookupPolicy,
|
||||
MetaclassCandidate, PEP695TypeAliasType, ParameterForm, SpecialFormType, SubclassOfType,
|
||||
TrackedConstraintSet, Truthiness, Type, TypeAliasType, TypeAndQualifiers, TypeContext,
|
||||
TypeQualifiers, TypeVarBoundOrConstraints, TypeVarBoundOrConstraintsEvaluation,
|
||||
TypeVarDefaultEvaluation, TypeVarIdentity, TypeVarInstance, TypeVarKind, TypeVarVariance,
|
||||
TypedDictType, UnionBuilder, UnionType, UnionTypeInstance, binding_type, infer_scope_types,
|
||||
overrides, todo_type,
|
||||
};
|
||||
use crate::types::{ClassBase, add_inferred_python_version_hint_to_diagnostic};
|
||||
use crate::unpack::{EvaluationMode, UnpackPosition};
|
||||
|
|
@ -11292,6 +11293,23 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
generic_context: GenericContext<'db>,
|
||||
specialize: impl FnOnce(&[Option<Type<'db>>]) -> Type<'db>,
|
||||
) -> Type<'db> {
|
||||
fn add_typevar_definition<'db>(
|
||||
db: &'db dyn Db,
|
||||
diagnostic: &mut Diagnostic,
|
||||
typevar: BoundTypeVarInstance<'db>,
|
||||
) {
|
||||
let Some(definition) = typevar.typevar(db).definition(db) else {
|
||||
return;
|
||||
};
|
||||
let file = definition.file(db);
|
||||
let module = parsed_module(db, file).load(db);
|
||||
let range = definition.focus_range(db, &module).range();
|
||||
diagnostic.annotate(
|
||||
Annotation::secondary(Span::from(file).with_range(range))
|
||||
.message("Type variable defined here"),
|
||||
);
|
||||
}
|
||||
|
||||
let db = self.db();
|
||||
let slice_node = subscript.slice.as_ref();
|
||||
|
||||
|
|
@ -11349,13 +11367,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
if let Some(builder) =
|
||||
self.context.report_lint(&INVALID_TYPE_ARGUMENTS, node)
|
||||
{
|
||||
builder.into_diagnostic(format_args!(
|
||||
let mut diagnostic = builder.into_diagnostic(format_args!(
|
||||
"Type `{}` is not assignable to upper bound `{}` \
|
||||
of type variable `{}`",
|
||||
provided_type.display(db),
|
||||
bound.display(db),
|
||||
typevar.identity(db).display(db),
|
||||
));
|
||||
add_typevar_definition(db, &mut diagnostic, typevar);
|
||||
}
|
||||
has_error = true;
|
||||
continue;
|
||||
|
|
@ -11374,7 +11393,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
if let Some(builder) =
|
||||
self.context.report_lint(&INVALID_TYPE_ARGUMENTS, node)
|
||||
{
|
||||
builder.into_diagnostic(format_args!(
|
||||
let mut diagnostic = builder.into_diagnostic(format_args!(
|
||||
"Type `{}` does not satisfy constraints `{}` \
|
||||
of type variable `{}`",
|
||||
provided_type.display(db),
|
||||
|
|
@ -11385,6 +11404,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
.format("`, `"),
|
||||
typevar.identity(db).display(db),
|
||||
));
|
||||
add_typevar_definition(db, &mut diagnostic, typevar);
|
||||
}
|
||||
has_error = true;
|
||||
continue;
|
||||
|
|
|
|||
Loading…
Reference in New Issue