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]
|
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
|
## Inferring generic class parameters
|
||||||
|
|
||||||
We can infer the type parameter from a type context:
|
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]
|
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
|
## Inferring generic class parameters
|
||||||
|
|
||||||
We can infer the type parameter from a type context:
|
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 std::iter;
|
||||||
|
|
||||||
use itertools::{Either, EitherOrBoth, Itertools};
|
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::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::visitor::{Visitor, walk_expr};
|
||||||
use ruff_python_ast::{
|
use ruff_python_ast::{
|
||||||
self as ast, AnyNodeRef, ExprContext, HasNodeIndex, NodeIndex, PythonVersion,
|
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::visitor::any_over_type;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
CallDunderError, CallableBinding, CallableType, CallableTypes, ClassLiteral, ClassType,
|
BoundTypeVarInstance, CallDunderError, CallableBinding, CallableType, CallableTypes,
|
||||||
DataclassParams, DynamicType, InternedType, IntersectionBuilder, IntersectionType, KnownClass,
|
ClassLiteral, ClassType, DataclassParams, DynamicType, InternedType, IntersectionBuilder,
|
||||||
KnownInstanceType, LintDiagnosticGuard, MemberLookupPolicy, MetaclassCandidate,
|
IntersectionType, KnownClass, KnownInstanceType, LintDiagnosticGuard, MemberLookupPolicy,
|
||||||
PEP695TypeAliasType, ParameterForm, SpecialFormType, SubclassOfType, TrackedConstraintSet,
|
MetaclassCandidate, PEP695TypeAliasType, ParameterForm, SpecialFormType, SubclassOfType,
|
||||||
Truthiness, Type, TypeAliasType, TypeAndQualifiers, TypeContext, TypeQualifiers,
|
TrackedConstraintSet, Truthiness, Type, TypeAliasType, TypeAndQualifiers, TypeContext,
|
||||||
TypeVarBoundOrConstraints, TypeVarBoundOrConstraintsEvaluation, TypeVarDefaultEvaluation,
|
TypeQualifiers, TypeVarBoundOrConstraints, TypeVarBoundOrConstraintsEvaluation,
|
||||||
TypeVarIdentity, TypeVarInstance, TypeVarKind, TypeVarVariance, TypedDictType, UnionBuilder,
|
TypeVarDefaultEvaluation, TypeVarIdentity, TypeVarInstance, TypeVarKind, TypeVarVariance,
|
||||||
UnionType, UnionTypeInstance, binding_type, infer_scope_types, overrides, todo_type,
|
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::types::{ClassBase, add_inferred_python_version_hint_to_diagnostic};
|
||||||
use crate::unpack::{EvaluationMode, UnpackPosition};
|
use crate::unpack::{EvaluationMode, UnpackPosition};
|
||||||
|
|
@ -11292,6 +11293,23 @@ 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> {
|
||||||
|
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 db = self.db();
|
||||||
let slice_node = subscript.slice.as_ref();
|
let slice_node = subscript.slice.as_ref();
|
||||||
|
|
||||||
|
|
@ -11349,13 +11367,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
if let Some(builder) =
|
if let Some(builder) =
|
||||||
self.context.report_lint(&INVALID_TYPE_ARGUMENTS, node)
|
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 `{}` \
|
"Type `{}` is not assignable to upper bound `{}` \
|
||||||
of type variable `{}`",
|
of type variable `{}`",
|
||||||
provided_type.display(db),
|
provided_type.display(db),
|
||||||
bound.display(db),
|
bound.display(db),
|
||||||
typevar.identity(db).display(db),
|
typevar.identity(db).display(db),
|
||||||
));
|
));
|
||||||
|
add_typevar_definition(db, &mut diagnostic, typevar);
|
||||||
}
|
}
|
||||||
has_error = true;
|
has_error = true;
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -11374,7 +11393,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
if let Some(builder) =
|
if let Some(builder) =
|
||||||
self.context.report_lint(&INVALID_TYPE_ARGUMENTS, node)
|
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 `{}` \
|
"Type `{}` does not satisfy constraints `{}` \
|
||||||
of type variable `{}`",
|
of type variable `{}`",
|
||||||
provided_type.display(db),
|
provided_type.display(db),
|
||||||
|
|
@ -11385,6 +11404,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
.format("`, `"),
|
.format("`, `"),
|
||||||
typevar.identity(db).display(db),
|
typevar.identity(db).display(db),
|
||||||
));
|
));
|
||||||
|
add_typevar_definition(db, &mut diagnostic, typevar);
|
||||||
}
|
}
|
||||||
has_error = true;
|
has_error = true;
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue