[ty] Improve disambiguation of types in many cases (#22019)

This commit is contained in:
Alex Waygood 2025-12-17 11:41:07 +00:00 committed by GitHub
parent 85af715880
commit 764ad8b29b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 173 additions and 12 deletions

View File

@ -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:

View File

@ -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
```

View File

@ -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'>)

View File

@ -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.

View File

@ -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
```

View File

@ -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
```

View File

@ -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",

View File

@ -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,
} }
} }