mirror of https://github.com/astral-sh/ruff
[ty] Improve disambiguation of types in many cases (#22019)
This commit is contained in:
parent
85af715880
commit
764ad8b29b
|
|
@ -1208,7 +1208,7 @@ def _(flag: bool):
|
||||||
reveal_type(C1.y) # revealed: int | str
|
reveal_type(C1.y) # revealed: int | str
|
||||||
|
|
||||||
C1.y = 100
|
C1.y = 100
|
||||||
# error: [invalid-assignment] "Object of type `Literal["problematic"]` is not assignable to attribute `y` on type `<class 'C1'> | <class 'C1'>`"
|
# error: [invalid-assignment] "Object of type `Literal["problematic"]` is not assignable to attribute `y` on type `<class 'mdtest_snippet.<locals of function '_'>.C1 @ src/mdtest_snippet.py:3'> | <class 'mdtest_snippet.<locals of function '_'>.C1 @ src/mdtest_snippet.py:8'>`"
|
||||||
C1.y = "problematic"
|
C1.y = "problematic"
|
||||||
|
|
||||||
class C2:
|
class C2:
|
||||||
|
|
|
||||||
|
|
@ -195,3 +195,52 @@ class C:
|
||||||
c = C()
|
c = C()
|
||||||
c.square("hello") # error: [invalid-argument-type]
|
c.square("hello") # error: [invalid-argument-type]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Types with the same name but from different files
|
||||||
|
|
||||||
|
`module.py`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
class Foo: ...
|
||||||
|
|
||||||
|
def needs_a_foo(x: Foo): ...
|
||||||
|
```
|
||||||
|
|
||||||
|
`main.py`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from module import needs_a_foo
|
||||||
|
|
||||||
|
class Foo: ...
|
||||||
|
|
||||||
|
needs_a_foo(Foo()) # error: [invalid-argument-type]
|
||||||
|
```
|
||||||
|
|
||||||
|
## TypeVars with bounds that have the same name but are from different files
|
||||||
|
|
||||||
|
In this case, using fully qualified names is *not* necessary.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.12"
|
||||||
|
```
|
||||||
|
|
||||||
|
`module.py`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
class Foo: ...
|
||||||
|
|
||||||
|
def needs_a_foo(x: Foo): ...
|
||||||
|
```
|
||||||
|
|
||||||
|
`main.py`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from module import needs_a_foo
|
||||||
|
|
||||||
|
class Foo: ...
|
||||||
|
|
||||||
|
def f[T: Foo](x: T) -> T:
|
||||||
|
needs_a_foo(x) # error: [invalid-argument-type]
|
||||||
|
return x
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -393,7 +393,7 @@ else:
|
||||||
# revealed: (<class 'B'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>) | (<class 'B'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>)
|
# revealed: (<class 'B'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>) | (<class 'B'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>)
|
||||||
reveal_mro(B)
|
reveal_mro(B)
|
||||||
|
|
||||||
# error: 12 [unsupported-base] "Unsupported class base with type `<class 'B'> | <class 'B'>`"
|
# error: 12 [unsupported-base] "Unsupported class base with type `<class 'mdtest_snippet.B @ src/mdtest_snippet.py:25'> | <class 'mdtest_snippet.B @ src/mdtest_snippet.py:28'>`"
|
||||||
class Z(A, B): ...
|
class Z(A, B): ...
|
||||||
|
|
||||||
reveal_mro(Z) # revealed: (<class 'Z'>, Unknown, <class 'object'>)
|
reveal_mro(Z) # revealed: (<class 'Z'>, Unknown, <class 'object'>)
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_as
|
||||||
# Diagnostics
|
# Diagnostics
|
||||||
|
|
||||||
```
|
```
|
||||||
error[invalid-assignment]: Object of type `Literal[1]` is not assignable to attribute `attr` on type `<class 'C1'> | <class 'C1'>`
|
error[invalid-assignment]: Object of type `Literal[1]` is not assignable to attribute `attr` on type `<class 'mdtest_snippet.<locals of function '_'>.C1 @ src/mdtest_snippet.py:3'> | <class 'mdtest_snippet.<locals of function '_'>.C1 @ src/mdtest_snippet.py:7'>`
|
||||||
--> src/mdtest_snippet.py:11:5
|
--> src/mdtest_snippet.py:11:5
|
||||||
|
|
|
|
||||||
10 | # TODO: The error message here could be improved to explain why the assignment fails.
|
10 | # TODO: The error message here could be improved to explain why the assignment fails.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
---
|
||||||
|
source: crates/ty_test/src/lib.rs
|
||||||
|
expression: snapshot
|
||||||
|
---
|
||||||
|
---
|
||||||
|
mdtest name: invalid_argument_type.md - Invalid argument type diagnostics - TypeVars with bounds that have the same name but are from different files
|
||||||
|
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argument_type.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# Python source files
|
||||||
|
|
||||||
|
## module.py
|
||||||
|
|
||||||
|
```
|
||||||
|
1 | class Foo: ...
|
||||||
|
2 |
|
||||||
|
3 | def needs_a_foo(x: Foo): ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## main.py
|
||||||
|
|
||||||
|
```
|
||||||
|
1 | from module import needs_a_foo
|
||||||
|
2 |
|
||||||
|
3 | class Foo: ...
|
||||||
|
4 |
|
||||||
|
5 | def f[T: Foo](x: T) -> T:
|
||||||
|
6 | needs_a_foo(x) # error: [invalid-argument-type]
|
||||||
|
7 | return x
|
||||||
|
```
|
||||||
|
|
||||||
|
# Diagnostics
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-argument-type]: Argument to function `needs_a_foo` is incorrect
|
||||||
|
--> src/main.py:6:17
|
||||||
|
|
|
||||||
|
5 | def f[T: Foo](x: T) -> T:
|
||||||
|
6 | needs_a_foo(x) # error: [invalid-argument-type]
|
||||||
|
| ^ Expected `Foo`, found `T@f`
|
||||||
|
7 | return x
|
||||||
|
|
|
||||||
|
info: Function defined here
|
||||||
|
--> src/module.py:3:5
|
||||||
|
|
|
||||||
|
1 | class Foo: ...
|
||||||
|
2 |
|
||||||
|
3 | def needs_a_foo(x: Foo): ...
|
||||||
|
| ^^^^^^^^^^^ ------ Parameter declared here
|
||||||
|
|
|
||||||
|
info: rule `invalid-argument-type` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
---
|
||||||
|
source: crates/ty_test/src/lib.rs
|
||||||
|
expression: snapshot
|
||||||
|
---
|
||||||
|
---
|
||||||
|
mdtest name: invalid_argument_type.md - Invalid argument type diagnostics - Types with the same name but from different files
|
||||||
|
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argument_type.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# Python source files
|
||||||
|
|
||||||
|
## module.py
|
||||||
|
|
||||||
|
```
|
||||||
|
1 | class Foo: ...
|
||||||
|
2 |
|
||||||
|
3 | def needs_a_foo(x: Foo): ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## main.py
|
||||||
|
|
||||||
|
```
|
||||||
|
1 | from module import needs_a_foo
|
||||||
|
2 |
|
||||||
|
3 | class Foo: ...
|
||||||
|
4 |
|
||||||
|
5 | needs_a_foo(Foo()) # error: [invalid-argument-type]
|
||||||
|
```
|
||||||
|
|
||||||
|
# Diagnostics
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-argument-type]: Argument to function `needs_a_foo` is incorrect
|
||||||
|
--> src/main.py:5:13
|
||||||
|
|
|
||||||
|
3 | class Foo: ...
|
||||||
|
4 |
|
||||||
|
5 | needs_a_foo(Foo()) # error: [invalid-argument-type]
|
||||||
|
| ^^^^^ Expected `module.Foo`, found `main.Foo`
|
||||||
|
|
|
||||||
|
info: Function defined here
|
||||||
|
--> src/module.py:3:5
|
||||||
|
|
|
||||||
|
1 | class Foo: ...
|
||||||
|
2 |
|
||||||
|
3 | def needs_a_foo(x: Foo): ...
|
||||||
|
| ^^^^^^^^^^^ ------ Parameter declared here
|
||||||
|
|
|
||||||
|
info: rule `invalid-argument-type` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
@ -21,7 +21,6 @@ use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use smallvec::{SmallVec, smallvec, smallvec_inline};
|
use smallvec::{SmallVec, smallvec, smallvec_inline};
|
||||||
|
|
||||||
use super::{Argument, CallArguments, CallError, CallErrorKind, InferContext, Signature, Type};
|
use super::{Argument, CallArguments, CallError, CallErrorKind, InferContext, Signature, Type};
|
||||||
use crate::Program;
|
|
||||||
use crate::db::Db;
|
use crate::db::Db;
|
||||||
use crate::dunder_all::dunder_all_names;
|
use crate::dunder_all::dunder_all_names;
|
||||||
use crate::module_resolver::KnownModule;
|
use crate::module_resolver::KnownModule;
|
||||||
|
|
@ -52,6 +51,7 @@ use crate::types::{
|
||||||
enums, list_members, todo_type,
|
enums, list_members, todo_type,
|
||||||
};
|
};
|
||||||
use crate::unpack::EvaluationMode;
|
use crate::unpack::EvaluationMode;
|
||||||
|
use crate::{DisplaySettings, Program};
|
||||||
use ruff_db::diagnostic::{Annotation, Diagnostic, SubDiagnostic, SubDiagnosticSeverity};
|
use ruff_db::diagnostic::{Annotation, Diagnostic, SubDiagnostic, SubDiagnosticSeverity};
|
||||||
use ruff_python_ast::{self as ast, ArgOrKeyword, PythonVersion};
|
use ruff_python_ast::{self as ast, ArgOrKeyword, PythonVersion};
|
||||||
|
|
||||||
|
|
@ -4156,8 +4156,13 @@ impl<'db> BindingError<'db> {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let provided_ty_display = provided_ty.display(context.db());
|
let display_settings = DisplaySettings::from_possibly_ambiguous_types(
|
||||||
let expected_ty_display = expected_ty.display(context.db());
|
context.db(),
|
||||||
|
[provided_ty, expected_ty],
|
||||||
|
);
|
||||||
|
let provided_ty_display =
|
||||||
|
provided_ty.display_with(context.db(), display_settings.clone());
|
||||||
|
let expected_ty_display = expected_ty.display_with(context.db(), display_settings);
|
||||||
|
|
||||||
let mut diag = builder.into_diagnostic(format_args!(
|
let mut diag = builder.into_diagnostic(format_args!(
|
||||||
"Argument{} is incorrect",
|
"Argument{} is incorrect",
|
||||||
|
|
|
||||||
|
|
@ -76,14 +76,15 @@ impl<'db> DisplaySettings<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn from_possibly_ambiguous_types(
|
pub fn from_possibly_ambiguous_types<I, T>(db: &'db dyn Db, types: I) -> Self
|
||||||
db: &'db dyn Db,
|
where
|
||||||
types: impl IntoIterator<Item = Type<'db>>,
|
I: IntoIterator<Item = T>,
|
||||||
) -> Self {
|
T: Into<Type<'db>>,
|
||||||
|
{
|
||||||
let collector = AmbiguousClassCollector::default();
|
let collector = AmbiguousClassCollector::default();
|
||||||
|
|
||||||
for ty in types {
|
for ty in types {
|
||||||
collector.visit_type(db, ty);
|
collector.visit_type(db, ty.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -422,6 +423,8 @@ impl<'db> super::visitor::TypeVisitor<'db> for AmbiguousClassCollector<'db> {
|
||||||
inner: Protocol::FromClass(class),
|
inner: Protocol::FromClass(class),
|
||||||
..
|
..
|
||||||
}) => return self.visit_type(db, Type::from(class)),
|
}) => return self.visit_type(db, Type::from(class)),
|
||||||
|
// no need to recurse into TypeVar bounds/constraints
|
||||||
|
Type::TypeVar(_) => return,
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -439,7 +442,7 @@ impl<'db> Type<'db> {
|
||||||
pub fn display(self, db: &'db dyn Db) -> DisplayType<'db> {
|
pub fn display(self, db: &'db dyn Db) -> DisplayType<'db> {
|
||||||
DisplayType {
|
DisplayType {
|
||||||
ty: self,
|
ty: self,
|
||||||
settings: DisplaySettings::default(),
|
settings: DisplaySettings::from_possibly_ambiguous_types(db, [self]),
|
||||||
db,
|
db,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue