From 764ad8b29b4956bcee24ac287f42e04a0500e56a Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 17 Dec 2025 11:41:07 +0000 Subject: [PATCH] [ty] Improve disambiguation of types in many cases (#22019) --- .../resources/mdtest/attributes.md | 2 +- .../diagnostics/invalid_argument_type.md | 49 +++++++++++++++++ .../resources/mdtest/mro.md | 2 +- ...etting_attributes_o…_(467e26496f4c0c13).snap | 2 +- ..._TypeVars_with_bounds…_(25b61918ea9f5644).snap | 53 +++++++++++++++++++ ..._Types_with_the_same_…_(34531e82322f6f21).snap | 51 ++++++++++++++++++ .../ty_python_semantic/src/types/call/bind.rs | 11 ++-- .../ty_python_semantic/src/types/display.rs | 15 +++--- 8 files changed, 173 insertions(+), 12 deletions(-) create mode 100644 crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ…_-_Invalid_argument_typ…_-_TypeVars_with_bounds…_(25b61918ea9f5644).snap create mode 100644 crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ…_-_Invalid_argument_typ…_-_Types_with_the_same_…_(34531e82322f6f21).snap diff --git a/crates/ty_python_semantic/resources/mdtest/attributes.md b/crates/ty_python_semantic/resources/mdtest/attributes.md index 3c28431d58..bbd74b266e 100644 --- a/crates/ty_python_semantic/resources/mdtest/attributes.md +++ b/crates/ty_python_semantic/resources/mdtest/attributes.md @@ -1208,7 +1208,7 @@ def _(flag: bool): reveal_type(C1.y) # revealed: int | str C1.y = 100 - # error: [invalid-assignment] "Object of type `Literal["problematic"]` is not assignable to attribute `y` on type ` | `" + # error: [invalid-assignment] "Object of type `Literal["problematic"]` is not assignable to attribute `y` on type `.C1 @ src/mdtest_snippet.py:3'> | .C1 @ src/mdtest_snippet.py:8'>`" C1.y = "problematic" class C2: diff --git a/crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argument_type.md b/crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argument_type.md index 794c29b6b9..8d4761dbde 100644 --- a/crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argument_type.md +++ b/crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argument_type.md @@ -195,3 +195,52 @@ class C: c = C() 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 +``` diff --git a/crates/ty_python_semantic/resources/mdtest/mro.md b/crates/ty_python_semantic/resources/mdtest/mro.md index 72e2360269..bba97a6ce8 100644 --- a/crates/ty_python_semantic/resources/mdtest/mro.md +++ b/crates/ty_python_semantic/resources/mdtest/mro.md @@ -393,7 +393,7 @@ else: # revealed: (, , , , ) | (, , , , ) reveal_mro(B) -# error: 12 [unsupported-base] "Unsupported class base with type ` | `" +# error: 12 [unsupported-base] "Unsupported class base with type ` | `" class Z(A, B): ... reveal_mro(Z) # revealed: (, Unknown, ) diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment…_-_Attribute_assignment_-_Setting_attributes_o…_(467e26496f4c0c13).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment…_-_Attribute_assignment_-_Setting_attributes_o…_(467e26496f4c0c13).snap index 7491ce72ae..5e7f50ebce 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment…_-_Attribute_assignment_-_Setting_attributes_o…_(467e26496f4c0c13).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment…_-_Attribute_assignment_-_Setting_attributes_o…_(467e26496f4c0c13).snap @@ -37,7 +37,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_as # Diagnostics ``` -error[invalid-assignment]: Object of type `Literal[1]` is not assignable to attribute `attr` on type ` | ` +error[invalid-assignment]: Object of type `Literal[1]` is not assignable to attribute `attr` on type `.C1 @ src/mdtest_snippet.py:3'> | .C1 @ src/mdtest_snippet.py:7'>` --> src/mdtest_snippet.py:11:5 | 10 | # TODO: The error message here could be improved to explain why the assignment fails. diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ…_-_Invalid_argument_typ…_-_TypeVars_with_bounds…_(25b61918ea9f5644).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ…_-_Invalid_argument_typ…_-_TypeVars_with_bounds…_(25b61918ea9f5644).snap new file mode 100644 index 0000000000..d19a4c0def --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ…_-_Invalid_argument_typ…_-_TypeVars_with_bounds…_(25b61918ea9f5644).snap @@ -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 + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ…_-_Invalid_argument_typ…_-_Types_with_the_same_…_(34531e82322f6f21).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ…_-_Invalid_argument_typ…_-_Types_with_the_same_…_(34531e82322f6f21).snap new file mode 100644 index 0000000000..01847da1dc --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_argument_typ…_-_Invalid_argument_typ…_-_Types_with_the_same_…_(34531e82322f6f21).snap @@ -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 + +``` diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 005013e70b..c0ff0036a0 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -21,7 +21,6 @@ use rustc_hash::{FxHashMap, FxHashSet}; use smallvec::{SmallVec, smallvec, smallvec_inline}; use super::{Argument, CallArguments, CallError, CallErrorKind, InferContext, Signature, Type}; -use crate::Program; use crate::db::Db; use crate::dunder_all::dunder_all_names; use crate::module_resolver::KnownModule; @@ -52,6 +51,7 @@ use crate::types::{ enums, list_members, todo_type, }; use crate::unpack::EvaluationMode; +use crate::{DisplaySettings, Program}; use ruff_db::diagnostic::{Annotation, Diagnostic, SubDiagnostic, SubDiagnosticSeverity}; use ruff_python_ast::{self as ast, ArgOrKeyword, PythonVersion}; @@ -4156,8 +4156,13 @@ impl<'db> BindingError<'db> { return; }; - let provided_ty_display = provided_ty.display(context.db()); - let expected_ty_display = expected_ty.display(context.db()); + let display_settings = DisplaySettings::from_possibly_ambiguous_types( + 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!( "Argument{} is incorrect", diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index 1120bff415..09a0f0509d 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -76,14 +76,15 @@ impl<'db> DisplaySettings<'db> { } #[must_use] - pub fn from_possibly_ambiguous_types( - db: &'db dyn Db, - types: impl IntoIterator>, - ) -> Self { + pub fn from_possibly_ambiguous_types(db: &'db dyn Db, types: I) -> Self + where + I: IntoIterator, + T: Into>, + { let collector = AmbiguousClassCollector::default(); for ty in types { - collector.visit_type(db, ty); + collector.visit_type(db, ty.into()); } Self { @@ -422,6 +423,8 @@ impl<'db> super::visitor::TypeVisitor<'db> for AmbiguousClassCollector<'db> { inner: Protocol::FromClass(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> { DisplayType { ty: self, - settings: DisplaySettings::default(), + settings: DisplaySettings::from_possibly_ambiguous_types(db, [self]), db, } }