mirror of
https://github.com/astral-sh/ruff
synced 2026-01-21 05:20:49 -05:00
[ty] Improve disambiguation of types (#22547)
This commit is contained in:
@@ -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 `<class 'mdtest_snippet.<locals of function '_'>.C1 @ src/mdtest_snippet.py:3'> | <class 'mdtest_snippet.<locals of function '_'>.C1 @ src/mdtest_snippet.py:8'>`"
|
||||
# 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:15'> | <class 'mdtest_snippet.<locals of function '_'>.C1 @ src/mdtest_snippet.py:8:15'>`"
|
||||
C1.y = "problematic"
|
||||
|
||||
class C2:
|
||||
|
||||
@@ -77,9 +77,9 @@ def takes_foo2(x: Foo2) -> None: ...
|
||||
takes_foo1(foo1) # OK
|
||||
takes_foo2(foo2) # OK
|
||||
|
||||
# error: [invalid-argument-type] "Argument to function `takes_foo1` is incorrect: Expected `mdtest_snippet.Foo @ src/mdtest_snippet.py:5`, found `mdtest_snippet.Foo @ src/mdtest_snippet.py:6`"
|
||||
# error: [invalid-argument-type] "Argument to function `takes_foo1` is incorrect: Expected `mdtest_snippet.Foo @ src/mdtest_snippet.py:5:8`, found `mdtest_snippet.Foo @ src/mdtest_snippet.py:6:8`"
|
||||
takes_foo1(foo2)
|
||||
# error: [invalid-argument-type] "Argument to function `takes_foo2` is incorrect: Expected `mdtest_snippet.Foo @ src/mdtest_snippet.py:6`, found `mdtest_snippet.Foo @ src/mdtest_snippet.py:5`"
|
||||
# error: [invalid-argument-type] "Argument to function `takes_foo2` is incorrect: Expected `mdtest_snippet.Foo @ src/mdtest_snippet.py:6:8`, found `mdtest_snippet.Foo @ src/mdtest_snippet.py:5:8`"
|
||||
takes_foo2(foo1)
|
||||
```
|
||||
|
||||
@@ -762,7 +762,7 @@ def make_classes(name1: str, name2: str):
|
||||
|
||||
def inner(x: cls1): ...
|
||||
|
||||
# error: [invalid-argument-type] "Argument to function `inner` is incorrect: Expected `mdtest_snippet.<locals of function 'make_classes'>.<unknown> @ src/mdtest_snippet.py:8`, found `mdtest_snippet.<locals of function 'make_classes'>.<unknown> @ src/mdtest_snippet.py:9`"
|
||||
# error: [invalid-argument-type] "Argument to function `inner` is incorrect: Expected `mdtest_snippet.<locals of function 'make_classes'>.<unknown> @ src/mdtest_snippet.py:8:12`, found `mdtest_snippet.<locals of function 'make_classes'>.<unknown> @ src/mdtest_snippet.py:9:12`"
|
||||
inner(cls2())
|
||||
```
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ class MyClass: ...
|
||||
def get_MyClass() -> MyClass:
|
||||
from . import make_MyClass
|
||||
|
||||
# error: [invalid-return-type] "Return type does not match returned value: expected `package.foo.MyClass @ src/package/foo.py:1`, found `package.foo.MyClass @ src/package/foo.pyi:1`"
|
||||
# error: [invalid-return-type] "Return type does not match returned value: expected `package.foo.MyClass @ src/package/foo.py:1:7`, found `package.foo.MyClass @ src/package/foo.pyi:1:7`"
|
||||
return make_MyClass()
|
||||
```
|
||||
|
||||
|
||||
@@ -398,10 +398,10 @@ if returns_bool():
|
||||
else:
|
||||
class B(Y, X): ...
|
||||
|
||||
# revealed: (<class 'B'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>) | (<class 'B'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>)
|
||||
# revealed: (<class 'mdtest_snippet.B @ src/mdtest_snippet.py:25:11'>, <class 'X'>, <class 'Y'>, <class 'O'>, <class 'object'>) | (<class 'mdtest_snippet.B @ src/mdtest_snippet.py:28:11'>, <class 'Y'>, <class 'X'>, <class 'O'>, <class 'object'>)
|
||||
reveal_mro(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'>`"
|
||||
# error: 12 [unsupported-base] "Unsupported class base with type `<class 'mdtest_snippet.B @ src/mdtest_snippet.py:25:11'> | <class 'mdtest_snippet.B @ src/mdtest_snippet.py:28:11'>`"
|
||||
class Z(A, B): ...
|
||||
|
||||
reveal_mro(Z) # revealed: (<class 'Z'>, Unknown, <class 'object'>)
|
||||
|
||||
@@ -339,7 +339,7 @@ class A: ...
|
||||
|
||||
def f(x: A):
|
||||
# TODO: no error
|
||||
# error: [invalid-assignment] "Object of type `mdtest_snippet.A @ src/mdtest_snippet.py:12 | mdtest_snippet.A @ src/mdtest_snippet.py:13` is not assignable to `mdtest_snippet.A @ src/mdtest_snippet.py:13`"
|
||||
# error: [invalid-assignment] "Object of type `mdtest_snippet.A @ src/mdtest_snippet.py:12:7 | mdtest_snippet.A @ src/mdtest_snippet.py:13:7` is not assignable to `mdtest_snippet.A @ src/mdtest_snippet.py:13:7`"
|
||||
x = A()
|
||||
```
|
||||
|
||||
|
||||
@@ -38,7 +38,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 `<class 'mdtest_snippet.<locals of function '_'>.C1 @ src/mdtest_snippet.py:3'> | <class 'mdtest_snippet.<locals of function '_'>.C1 @ src/mdtest_snippet.py:7'>`
|
||||
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:15'> | <class 'mdtest_snippet.<locals of function '_'>.C1 @ src/mdtest_snippet.py:7:15'>`
|
||||
--> src/mdtest_snippet.py:11:5
|
||||
|
|
||||
10 | # TODO: The error message here could be improved to explain why the assignment fails.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
|
||||
---
|
||||
mdtest name: attributes.md - Attributes - Diagnostic for function attribute accessed on `Callable` type
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/attributes.md
|
||||
|
||||
@@ -67,7 +67,7 @@ warning[unsupported-base]: Unsupported class base
|
||||
25 | class C: ...
|
||||
26 |
|
||||
27 | class D(C): ... # error: [unsupported-base]
|
||||
| ^ Has type `<class 'mdtest_snippet.<locals of function 'f'>.C @ src/mdtest_snippet.py:23'> | <class 'mdtest_snippet.<locals of function 'f'>.C @ src/mdtest_snippet.py:25'>`
|
||||
| ^ Has type `<class 'mdtest_snippet.<locals of function 'f'>.C @ src/mdtest_snippet.py:23:15'> | <class 'mdtest_snippet.<locals of function 'f'>.C @ src/mdtest_snippet.py:25:15'>`
|
||||
|
|
||||
info: ty cannot resolve a consistent method resolution order (MRO) for class `D` due to this base
|
||||
info: Only class objects or `Any` are supported as class bases
|
||||
|
||||
@@ -33,7 +33,7 @@ error[invalid-super-argument]: Argument is not a valid class
|
||||
7 | else:
|
||||
8 | class A: ...
|
||||
9 | super(A, A()) # error: [invalid-super-argument]
|
||||
| ^^^^^^^^^^^^^ Argument has type `<class 'mdtest_snippet.<locals of function 'f'>.A @ src/mdtest_snippet.py:6'> | <class 'mdtest_snippet.<locals of function 'f'>.A @ src/mdtest_snippet.py:8'>`
|
||||
| ^^^^^^^^^^^^^ Argument has type `<class 'mdtest_snippet.<locals of function 'f'>.A @ src/mdtest_snippet.py:6:15'> | <class 'mdtest_snippet.<locals of function 'f'>.A @ src/mdtest_snippet.py:8:15'>`
|
||||
|
|
||||
info: rule `invalid-super-argument` is enabled by default
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::Db;
|
||||
use crate::types::class::CodeGeneratorKind;
|
||||
use crate::types::generics::{ApplySpecialization, Specialization};
|
||||
use crate::types::mro::MroIterator;
|
||||
use crate::{Db, DisplaySettings};
|
||||
|
||||
use crate::types::tuple::TupleType;
|
||||
use crate::types::{
|
||||
@@ -408,16 +408,27 @@ impl<'db> ClassBase<'db> {
|
||||
}
|
||||
|
||||
pub(super) fn display(self, db: &'db dyn Db) -> impl std::fmt::Display {
|
||||
self.display_with(db, DisplaySettings::default())
|
||||
}
|
||||
|
||||
pub(super) fn display_with(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
display_settings: DisplaySettings<'db>,
|
||||
) -> impl std::fmt::Display {
|
||||
struct ClassBaseDisplay<'db> {
|
||||
db: &'db dyn Db,
|
||||
base: ClassBase<'db>,
|
||||
settings: DisplaySettings<'db>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ClassBaseDisplay<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.base {
|
||||
ClassBase::Dynamic(dynamic) => dynamic.fmt(f),
|
||||
ClassBase::Class(class) => Type::from(class).display(self.db).fmt(f),
|
||||
ClassBase::Class(class) => Type::from(class)
|
||||
.display_with(self.db, self.settings.clone())
|
||||
.fmt(f),
|
||||
ClassBase::Protocol => f.write_str("typing.Protocol"),
|
||||
ClassBase::Generic => f.write_str("typing.Generic"),
|
||||
ClassBase::TypedDict => f.write_str("typing.TypedDict"),
|
||||
@@ -425,7 +436,11 @@ impl<'db> ClassBase<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
ClassBaseDisplay { db, base: self }
|
||||
ClassBaseDisplay {
|
||||
db,
|
||||
base: self,
|
||||
settings: display_settings,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,10 @@ use std::fmt::{self, Display, Formatter, Write};
|
||||
use std::rc::Rc;
|
||||
|
||||
use ruff_db::files::FilePath;
|
||||
use ruff_db::source::line_index;
|
||||
use ruff_db::source::{line_index, source_text};
|
||||
use ruff_python_ast::str::{Quote, TripleQuotes};
|
||||
use ruff_python_literal::escape::AsciiEscape;
|
||||
use ruff_source_file::LineColumn;
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
@@ -581,9 +582,10 @@ impl<'db> FmtDetailed<'db> for ClassDisplay<'db> {
|
||||
FilePath::Vendored(_) | FilePath::SystemVirtual(_) => Cow::Borrowed(path),
|
||||
};
|
||||
let line_index = line_index(self.db, file);
|
||||
let line_number = line_index.line_index(class_offset);
|
||||
let LineColumn { line, column } =
|
||||
line_index.line_column(class_offset, &source_text(self.db, file));
|
||||
f.set_invalid_type_annotation();
|
||||
write!(f, " @ {path}:{line_number}")?;
|
||||
write!(f, " @ {path}:{line}:{column}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1818,10 +1818,19 @@ impl KnownFunction {
|
||||
let mut diag = builder.into_diagnostic("Revealed MRO");
|
||||
let span = context.span(&call_expression.arguments.args[0]);
|
||||
let mut message = String::new();
|
||||
let display_settings = DisplaySettings::from_possibly_ambiguous_types(
|
||||
db,
|
||||
classes
|
||||
.iter()
|
||||
.flat_map(|class| class.iter_mro(db))
|
||||
.filter_map(ClassBase::into_class),
|
||||
);
|
||||
for (i, class) in classes.iter().enumerate() {
|
||||
message.push('(');
|
||||
for class in class.iter_mro(db) {
|
||||
message.push_str(&class.display(db).to_string());
|
||||
message.push_str(
|
||||
&class.display_with(db, display_settings.clone()).to_string(),
|
||||
);
|
||||
// Omit the comma for the last element (which is always `object`)
|
||||
if class
|
||||
.into_class()
|
||||
|
||||
@@ -231,11 +231,11 @@ fn discard_todo_metadata(ty: &str) -> Cow<'_, str> {
|
||||
/// Normalize paths in diagnostics to Unix paths before comparing them against
|
||||
/// the expected type. Doing otherwise means that it's hard to write cross-platform
|
||||
/// tests, since in some edge cases the display of a type can include a path to the
|
||||
/// file in which the type was defined (e.g. `foo.bar.A @ src/foo/bar.py:10` on Unix,
|
||||
/// but `foo.bar.A @ src\foo\bar.py:10` on Windows).
|
||||
/// file in which the type was defined (e.g. `foo.bar.A @ src/foo/bar.py:10:5` on Unix,
|
||||
/// but `foo.bar.A @ src\foo\bar.py:10:5` on Windows).
|
||||
fn normalize_paths(ty: &str) -> Cow<'_, str> {
|
||||
static PATH_IN_CLASS_DISPLAY_REGEX: LazyLock<regex::Regex> =
|
||||
LazyLock::new(|| regex::Regex::new(r"( @ )(.+)(\.pyi?:\d)").unwrap());
|
||||
LazyLock::new(|| regex::Regex::new(r"( @ )([^\.]+?)(\.pyi?:\d)").unwrap());
|
||||
|
||||
fn normalize_path_captures(path_captures: ®ex::Captures) -> String {
|
||||
let normalized_path = std::path::Path::new(&path_captures[2])
|
||||
@@ -360,6 +360,8 @@ impl Matcher {
|
||||
return false;
|
||||
};
|
||||
|
||||
let primary_annotation = normalize_paths(primary_annotation);
|
||||
|
||||
// reveal_type, reveal_protocol_interface
|
||||
if matches!(
|
||||
primary_message,
|
||||
|
||||
Reference in New Issue
Block a user