diff --git a/crates/ty/tests/cli/python_environment.rs b/crates/ty/tests/cli/python_environment.rs index 4f3e442473..04fa8be88f 100644 --- a/crates/ty/tests/cli/python_environment.rs +++ b/crates/ty/tests/cli/python_environment.rs @@ -30,7 +30,7 @@ fn config_override_python_version() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error[unresolved-attribute]: Type `` has no attribute `last_exc` + error[unresolved-attribute]: Module `sys` has no member `last_exc` --> test.py:5:7 | 4 | # Access `sys.last_exc` that was only added in Python 3.12 @@ -962,7 +962,7 @@ fn defaults_to_a_new_python_version() -> anyhow::Result<()> { success: false exit_code: 1 ----- stdout ----- - error[unresolved-attribute]: Type `` has no attribute `grantpt` + error[unresolved-attribute]: Module `os` has no member `grantpt` --> main.py:4:1 | 2 | import os diff --git a/crates/ty/tests/file_watching.rs b/crates/ty/tests/file_watching.rs index 950cf82a84..5a738b5fd5 100644 --- a/crates/ty/tests/file_watching.rs +++ b/crates/ty/tests/file_watching.rs @@ -1112,11 +1112,11 @@ print(sys.last_exc, os.getegid()) assert_eq!(diagnostics.len(), 2); assert_eq!( diagnostics[0].primary_message(), - "Type `` has no attribute `last_exc`" + "Module `sys` has no member `last_exc`" ); assert_eq!( diagnostics[1].primary_message(), - "Type `` has no attribute `getegid`" + "Module `os` has no member `getegid`" ); // Change the python version diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/callable.md b/crates/ty_python_semantic/resources/mdtest/annotations/callable.md index 7ffb49bb72..780b2a87db 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/callable.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/callable.md @@ -366,7 +366,7 @@ on function-like callables: ```py def f_wrong(c: Callable[[], None]): - # error: [unresolved-attribute] "Type `() -> None` has no attribute `__qualname__`" + # error: [unresolved-attribute] "Object of type `() -> None` has no attribute `__qualname__`" c.__qualname__ # error: [unresolved-attribute] "Unresolved attribute `__qualname__` on type `() -> None`." diff --git a/crates/ty_python_semantic/resources/mdtest/attributes.md b/crates/ty_python_semantic/resources/mdtest/attributes.md index 3c910ee77e..a71a40578b 100644 --- a/crates/ty_python_semantic/resources/mdtest/attributes.md +++ b/crates/ty_python_semantic/resources/mdtest/attributes.md @@ -1260,13 +1260,13 @@ def _(flag1: bool, flag2: bool): C = C1 if flag1 else C2 if flag2 else C3 - # error: [possibly-missing-attribute] "Attribute `x` on type ` | | ` may be missing" + # error: [possibly-missing-attribute] "Attribute `x` may be missing on object of type ` | | `" reveal_type(C.x) # revealed: Unknown | Literal[1, 3] # error: [invalid-assignment] "Object of type `Literal[100]` is not assignable to attribute `x` on type ` | | `" C.x = 100 - # error: [possibly-missing-attribute] "Attribute `x` on type `C1 | C2 | C3` may be missing" + # error: [possibly-missing-attribute] "Attribute `x` may be missing on object of type `C1 | C2 | C3`" reveal_type(C().x) # revealed: Unknown | Literal[1, 3] # error: [invalid-assignment] "Object of type `Literal[100]` is not assignable to attribute `x` on type `C1 | C2 | C3`" @@ -1292,7 +1292,7 @@ def _(flag: bool, flag1: bool, flag2: bool): C = C1 if flag1 else C2 if flag2 else C3 - # error: [possibly-missing-attribute] "Attribute `x` on type ` | | ` may be missing" + # error: [possibly-missing-attribute] "Attribute `x` may be missing on object of type ` | | `" reveal_type(C.x) # revealed: Unknown | Literal[1, 2, 3] # error: [possibly-missing-attribute] @@ -1300,7 +1300,7 @@ def _(flag: bool, flag1: bool, flag2: bool): # Note: we might want to consider ignoring possibly-missing diagnostics for instance attributes eventually, # see the "Possibly unbound/undeclared instance attribute" section below. - # error: [possibly-missing-attribute] "Attribute `x` on type `C1 | C2 | C3` may be missing" + # error: [possibly-missing-attribute] "Attribute `x` may be missing on object of type `C1 | C2 | C3`" reveal_type(C().x) # revealed: Unknown | Literal[1, 2, 3] # error: [possibly-missing-attribute] @@ -1433,7 +1433,7 @@ def _(flag: bool): class C2: ... C = C1 if flag else C2 - # error: [unresolved-attribute] "Type ` | ` has no attribute `x`" + # error: [unresolved-attribute] "Object of type ` | ` has no attribute `x`" reveal_type(C.x) # revealed: Unknown # TODO: This should ideally be a `unresolved-attribute` error. We need better union @@ -1771,7 +1771,7 @@ reveal_type(date.day) # revealed: int reveal_type(date.month) # revealed: int reveal_type(date.year) # revealed: int -# error: [unresolved-attribute] "Type `Date` has no attribute `century`" +# error: [unresolved-attribute] "Object of type `Date` has no attribute `century`" reveal_type(date.century) # revealed: Unknown ``` diff --git a/crates/ty_python_semantic/resources/mdtest/call/methods.md b/crates/ty_python_semantic/resources/mdtest/call/methods.md index b6547e338d..8179e3272d 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/methods.md +++ b/crates/ty_python_semantic/resources/mdtest/call/methods.md @@ -311,7 +311,7 @@ reveal_type(C.f(1)) # revealed: str The method `f` can not be accessed from an instance of the class: ```py -# error: [unresolved-attribute] "Type `C` has no attribute `f`" +# error: [unresolved-attribute] "Object of type `C` has no attribute `f`" C().f ``` diff --git a/crates/ty_python_semantic/resources/mdtest/class/super.md b/crates/ty_python_semantic/resources/mdtest/class/super.md index 933e43bbd1..1862866764 100644 --- a/crates/ty_python_semantic/resources/mdtest/class/super.md +++ b/crates/ty_python_semantic/resources/mdtest/class/super.md @@ -308,7 +308,7 @@ class B(A): ... reveal_type(super(B)) # revealed: super -# error: [unresolved-attribute] "Type `super` has no attribute `a`" +# error: [unresolved-attribute] "Object of type `super` has no attribute `a`" super(B).a ``` @@ -436,7 +436,7 @@ def f(x: C | D): s = super(A, x) reveal_type(s) # revealed: , C> | , D> - # error: [possibly-missing-attribute] "Attribute `b` on type `, C> | , D>` may be missing" + # error: [possibly-missing-attribute] "Attribute `b` may be missing on object of type `, C> | , D>`" s.b def f(flag: bool): @@ -476,7 +476,7 @@ def f(flag: bool): reveal_type(s.x) # revealed: Unknown | Literal[1, 2] reveal_type(s.y) # revealed: int | str - # error: [possibly-missing-attribute] "Attribute `a` on type `, B> | , D>` may be missing" + # error: [possibly-missing-attribute] "Attribute `a` may be missing on object of type `, B> | , D>`" reveal_type(s.a) # revealed: str ``` @@ -619,7 +619,7 @@ class B(A): # TODO: Once `Self` is supported, this should raise `unresolved-attribute` error super().a -# error: [unresolved-attribute] "Type `, B>` has no attribute `a`" +# error: [unresolved-attribute] "Object of type `, B>` has no attribute `a`" super(B, B(42)).a ``` diff --git a/crates/ty_python_semantic/resources/mdtest/descriptor_protocol.md b/crates/ty_python_semantic/resources/mdtest/descriptor_protocol.md index 9210c84483..f5e0f254d0 100644 --- a/crates/ty_python_semantic/resources/mdtest/descriptor_protocol.md +++ b/crates/ty_python_semantic/resources/mdtest/descriptor_protocol.md @@ -758,16 +758,16 @@ def _(flag: bool): non_data: NonDataDescriptor = NonDataDescriptor() data: DataDescriptor = DataDescriptor() - # error: [possibly-missing-attribute] "Attribute `non_data` on type `` may be missing" + # error: [possibly-missing-attribute] "Attribute `non_data` may be missing on class `PossiblyUnbound`" reveal_type(PossiblyUnbound.non_data) # revealed: int - # error: [possibly-missing-attribute] "Attribute `non_data` on type `PossiblyUnbound` may be missing" + # error: [possibly-missing-attribute] "Attribute `non_data` may be missing on object of type `PossiblyUnbound`" reveal_type(PossiblyUnbound().non_data) # revealed: int - # error: [possibly-missing-attribute] "Attribute `data` on type `` may be missing" + # error: [possibly-missing-attribute] "Attribute `data` may be missing on class `PossiblyUnbound`" reveal_type(PossiblyUnbound.data) # revealed: int - # error: [possibly-missing-attribute] "Attribute `data` on type `PossiblyUnbound` may be missing" + # error: [possibly-missing-attribute] "Attribute `data` may be missing on object of type `PossiblyUnbound`" reveal_type(PossiblyUnbound().data) # revealed: int ``` diff --git a/crates/ty_python_semantic/resources/mdtest/expression/attribute.md b/crates/ty_python_semantic/resources/mdtest/expression/attribute.md index f59da0bc48..60c1bc518a 100644 --- a/crates/ty_python_semantic/resources/mdtest/expression/attribute.md +++ b/crates/ty_python_semantic/resources/mdtest/expression/attribute.md @@ -26,9 +26,9 @@ def _(flag: bool): reveal_type(A.union_declared) # revealed: int | str - # error: [possibly-missing-attribute] "Attribute `possibly_unbound` on type `` may be missing" + # error: [possibly-missing-attribute] "Attribute `possibly_unbound` may be missing on class `A`" reveal_type(A.possibly_unbound) # revealed: str - # error: [unresolved-attribute] "Type `` has no attribute `non_existent`" + # error: [unresolved-attribute] "Class `A` has no attribute `non_existent`" reveal_type(A.non_existent) # revealed: Unknown ``` diff --git a/crates/ty_python_semantic/resources/mdtest/import/relative.md b/crates/ty_python_semantic/resources/mdtest/import/relative.md index 6621172404..a80735e460 100644 --- a/crates/ty_python_semantic/resources/mdtest/import/relative.md +++ b/crates/ty_python_semantic/resources/mdtest/import/relative.md @@ -247,7 +247,7 @@ X: int = 42 from . import foo import package -# error: [unresolved-attribute] "Type `` has no attribute `foo`" +# error: [unresolved-attribute] "Module `package` has no member `foo`" reveal_type(package.foo.X) # revealed: Unknown ``` diff --git a/crates/ty_python_semantic/resources/mdtest/narrow/hasattr.md b/crates/ty_python_semantic/resources/mdtest/narrow/hasattr.md index ab38563ee7..633dd83bd2 100644 --- a/crates/ty_python_semantic/resources/mdtest/narrow/hasattr.md +++ b/crates/ty_python_semantic/resources/mdtest/narrow/hasattr.md @@ -95,6 +95,6 @@ def f(x: object): reveal_type(x.__str__) # revealed: bound method object.__str__() -> str reveal_type(x.__dict__) # revealed: dict[str, Any] - # error: [unresolved-attribute] "Type `` has no attribute `foo`" + # error: [unresolved-attribute] "Object of type `` has no attribute `foo`" reveal_type(x.foo) # revealed: Unknown ``` diff --git a/crates/ty_python_semantic/resources/mdtest/scopes/unbound.md b/crates/ty_python_semantic/resources/mdtest/scopes/unbound.md index 43c8f5f564..bf1f81d5c2 100644 --- a/crates/ty_python_semantic/resources/mdtest/scopes/unbound.md +++ b/crates/ty_python_semantic/resources/mdtest/scopes/unbound.md @@ -16,7 +16,7 @@ class C: if flag: x = 2 -# error: [possibly-missing-attribute] "Attribute `x` on type `` may be missing" +# error: [possibly-missing-attribute] "Attribute `x` may be missing on class `C`" reveal_type(C.x) # revealed: Unknown | Literal[2] reveal_type(C.y) # revealed: Unknown | Literal[1] ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment…_-_Attribute_assignment_-_Possibly-missing_att…_(e603e3da35f55c73).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment…_-_Attribute_assignment_-_Possibly-missing_att…_(e603e3da35f55c73).snap index 9335fec6da..420b92e5a7 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment…_-_Attribute_assignment_-_Possibly-missing_att…_(e603e3da35f55c73).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment…_-_Attribute_assignment_-_Possibly-missing_att…_(e603e3da35f55c73).snap @@ -26,7 +26,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_as # Diagnostics ``` -warning[possibly-missing-attribute]: Attribute `attr` on type `` may be missing +warning[possibly-missing-attribute]: Attribute `attr` may be missing on class `C` --> src/mdtest_snippet.py:6:5 | 4 | attr: int = 0 @@ -41,7 +41,7 @@ info: rule `possibly-missing-attribute` is enabled by default ``` ``` -warning[possibly-missing-attribute]: Attribute `attr` on type `C` may be missing +warning[possibly-missing-attribute]: Attribute `attr` may be missing on object of type `C` --> src/mdtest_snippet.py:9:5 | 8 | instance = C() diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Attributes_of_standa…_(49ba2c9016d64653).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Attributes_of_standa…_(49ba2c9016d64653).snap index b5171a701c..a1cd9d301c 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Attributes_of_standa…_(49ba2c9016d64653).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Attributes_of_standa…_(49ba2c9016d64653).snap @@ -23,7 +23,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/attributes.md # Diagnostics ``` -error[unresolved-attribute]: Type `` has no attribute `UTC` +error[unresolved-attribute]: Module `datetime` has no member `UTC` --> src/main.py:4:13 | 3 | # error: [unresolved-attribute] @@ -38,7 +38,7 @@ info: rule `unresolved-attribute` is enabled by default ``` ``` -error[unresolved-attribute]: Type `` has no attribute `fakenotreal` +error[unresolved-attribute]: Module `datetime` has no member `fakenotreal` --> src/main.py:6:13 | 4 | reveal_type(datetime.UTC) # revealed: Unknown diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/super.md_-_Super_-_Basic_Usage_-_Explicit_Super_Objec…_(b753048091f275c0).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/super.md_-_Super_-_Basic_Usage_-_Explicit_Super_Objec…_(b753048091f275c0).snap index 014d05eff4..873e98e2dc 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/super.md_-_Super_-_Basic_Usage_-_Explicit_Super_Objec…_(b753048091f275c0).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/super.md_-_Super_-_Basic_Usage_-_Explicit_Super_Objec…_(b753048091f275c0).snap @@ -118,7 +118,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/class/super.md # Diagnostics ``` -error[unresolved-attribute]: Type `, C>` has no attribute `c` +error[unresolved-attribute]: Object of type `, C>` has no attribute `c` --> src/mdtest_snippet.py:19:1 | 17 | super(C, C()).a @@ -133,7 +133,7 @@ info: rule `unresolved-attribute` is enabled by default ``` ``` -error[unresolved-attribute]: Type `, C>` has no attribute `b` +error[unresolved-attribute]: Object of type `, C>` has no attribute `b` --> src/mdtest_snippet.py:22:1 | 21 | super(B, C()).a @@ -146,7 +146,7 @@ info: rule `unresolved-attribute` is enabled by default ``` ``` -error[unresolved-attribute]: Type `, C>` has no attribute `c` +error[unresolved-attribute]: Object of type `, C>` has no attribute `c` --> src/mdtest_snippet.py:23:1 | 21 | super(B, C()).a @@ -161,7 +161,7 @@ info: rule `unresolved-attribute` is enabled by default ``` ``` -error[unresolved-attribute]: Type `, C>` has no attribute `a` +error[unresolved-attribute]: Object of type `, C>` has no attribute `a` --> src/mdtest_snippet.py:25:1 | 23 | super(B, C()).c # error: [unresolved-attribute] @@ -176,7 +176,7 @@ info: rule `unresolved-attribute` is enabled by default ``` ``` -error[unresolved-attribute]: Type `, C>` has no attribute `b` +error[unresolved-attribute]: Object of type `, C>` has no attribute `b` --> src/mdtest_snippet.py:26:1 | 25 | super(A, C()).a # error: [unresolved-attribute] @@ -189,7 +189,7 @@ info: rule `unresolved-attribute` is enabled by default ``` ``` -error[unresolved-attribute]: Type `, C>` has no attribute `c` +error[unresolved-attribute]: Object of type `, C>` has no attribute `c` --> src/mdtest_snippet.py:27:1 | 25 | super(A, C()).a # error: [unresolved-attribute] diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md index 624c5884c8..41c7f562bb 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md @@ -618,7 +618,7 @@ import importlib from module2 import importlib as other_importlib from ty_extensions import TypeOf, static_assert, is_equivalent_to -# error: [unresolved-attribute] "Type `` has no attribute `abc`" +# error: [unresolved-attribute] "Module `importlib` has no member `abc`" reveal_type(importlib.abc) # revealed: Unknown reveal_type(other_importlib.abc) # revealed: diff --git a/crates/ty_python_semantic/resources/mdtest/typed_dict.md b/crates/ty_python_semantic/resources/mdtest/typed_dict.md index 5cefed9b28..d810a79efe 100644 --- a/crates/ty_python_semantic/resources/mdtest/typed_dict.md +++ b/crates/ty_python_semantic/resources/mdtest/typed_dict.md @@ -672,18 +672,18 @@ Also, the "attributes" on the class definition can not be accessed. Neither on t on inhabitants of the type defined by the class: ```py -# error: [unresolved-attribute] "Type `` has no attribute `name`" +# error: [unresolved-attribute] "Class `Person` has no attribute `name`" Person.name def _(P: type[Person]): - # error: [unresolved-attribute] "Type `type[Person]` has no attribute `name`" + # error: [unresolved-attribute] "Object of type `type[Person]` has no attribute `name`" P.name def _(p: Person) -> None: - # error: [unresolved-attribute] "Type `Person` has no attribute `name`" + # error: [unresolved-attribute] "Object of type `Person` has no attribute `name`" p.name - type(p).name # error: [unresolved-attribute] "Type `` has no attribute `name`" + type(p).name # error: [unresolved-attribute] "Class `dict[str, object]` has no attribute `name`" ``` ## Special properties diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index 8046297079..7db83b9b88 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -2269,10 +2269,25 @@ pub(super) fn report_possibly_missing_attribute( let Some(builder) = context.report_lint(&POSSIBLY_MISSING_ATTRIBUTE, target) else { return; }; - builder.into_diagnostic(format_args!( - "Attribute `{attribute}` on type `{}` may be missing", - object_ty.display(context.db()), - )); + let db = context.db(); + match object_ty { + Type::ModuleLiteral(module) => builder.into_diagnostic(format_args!( + "Member `{attribute}` may be missing on module `{}`", + module.module(db).name(db), + )), + Type::ClassLiteral(class) => builder.into_diagnostic(format_args!( + "Attribute `{attribute}` may be missing on class `{}`", + class.name(db), + )), + Type::GenericAlias(alias) => builder.into_diagnostic(format_args!( + "Attribute `{attribute}` may be missing on class `{}`", + alias.display(db), + )), + _ => builder.into_diagnostic(format_args!( + "Attribute `{attribute}` may be missing on object of type `{}`", + object_ty.display(db), + )), + }; } pub(super) fn report_invalid_exception_caught(context: &InferContext, node: &ast::Expr, ty: Type) { diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index 5b2f69e811..560943aba1 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -814,6 +814,10 @@ impl Display for DisplayFunctionType<'_> { } impl<'db> GenericAlias<'db> { + pub(crate) fn display(&'db self, db: &'db dyn Db) -> DisplayGenericAlias<'db> { + self.display_with(db, DisplaySettings::default()) + } + pub(crate) fn display_with( &'db self, db: &'db dyn Db, diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index e0ad80ac06..44ed5841f8 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -7618,25 +7618,45 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { .context .report_lint(&UNRESOLVED_ATTRIBUTE, attribute) { - if bound_on_instance { - builder.into_diagnostic( - format_args!( - "Attribute `{}` can only be accessed on instances, \ - not on the class object `{}` itself.", - attr.id, - value_type.display(db) - ), - ); - } else { - let diagnostic = builder.into_diagnostic( - format_args!( - "Type `{}` has no attribute `{}`", - value_type.display(db), - attr.id - ), - ); - hint_if_stdlib_attribute_exists_on_other_versions(db, diagnostic, &value_type, attr); - } + if bound_on_instance { + builder.into_diagnostic( + format_args!( + "Attribute `{}` can only be accessed on instances, \ + not on the class object `{}` itself.", + attr.id, + value_type.display(db) + ), + ); + } else { + let diagnostic = match value_type { + Type::ModuleLiteral(module) => builder.into_diagnostic(format_args!( + "Module `{}` has no member `{}`", + module.module(db).name(db), + &attr.id + )), + Type::ClassLiteral(class) => builder.into_diagnostic(format_args!( + "Class `{}` has no attribute `{}`", + class.name(db), + &attr.id + )), + Type::GenericAlias(alias) => builder.into_diagnostic(format_args!( + "Class `{}` has no attribute `{}`", + alias.display(db), + &attr.id + )), + Type::FunctionLiteral(function) => builder.into_diagnostic(format_args!( + "Function `{}` has no attribute `{}`", + function.name(db), + &attr.id + )), + _ => builder.into_diagnostic(format_args!( + "Object of type `{}` has no attribute `{}`", + value_type.display(db), + &attr.id + )), + }; + hint_if_stdlib_attribute_exists_on_other_versions(db, diagnostic, &value_type, attr); + } } }