From bf38e69870c630e5ef84303a6b8b4f6ab7f15cc7 Mon Sep 17 00:00:00 2001 From: Renkai Ge Date: Tue, 23 Sep 2025 22:26:55 +0800 Subject: [PATCH] [ty] Rename "possibly unbound" diagnostics to "possibly missing" (#20492) Co-authored-by: Alex Waygood --- crates/ty/docs/environment.md | 7 +++ crates/ty/docs/rules.md | 24 ++++----- .../resources/mdtest/attributes.md | 52 +++++++++---------- .../mdtest/boundness_declaredness/public.md | 12 ++--- .../mdtest/call/callable_instance.md | 8 +-- .../resources/mdtest/call/constructor.md | 14 ++--- .../resources/mdtest/call/dunder.md | 13 ++++- .../resources/mdtest/call/methods.md | 2 +- .../resources/mdtest/class/super.md | 4 +- .../resources/mdtest/descriptor_protocol.md | 18 +++---- .../diagnostics/attribute_assignment.md | 6 +-- .../mdtest/diagnostics/invalid_await.md | 2 +- .../mdtest/diagnostics/union_call.md | 2 +- .../resources/mdtest/expression/attribute.md | 2 +- .../resources/mdtest/import/conditional.md | 4 +- .../resources/mdtest/import/conventions.md | 6 +-- .../resources/mdtest/loops/async_for.md | 4 +- .../resources/mdtest/loops/for.md | 14 ++--- .../resources/mdtest/narrow/assignment.md | 8 +-- .../mdtest/narrow/conditionals/nested.md | 2 +- .../resources/mdtest/narrow/hasattr.md | 6 +-- .../resources/mdtest/scopes/unbound.md | 4 +- ...ssibly_missing_`__…_(33924dbae5117216).snap} | 2 +- ...ssibly_missing_`__…_(e2600ca4708d9e54).snap} | 2 +- ...-_Possibly-missing_att…_(e603e3da35f55c73).snap} | 18 +++---- ...ssibly_missing_`__…_(77269542b8e81774).snap} | 2 +- ...ssibly_missing_`__…_(9f781babda99d74b).snap} | 2 +- ...ssibly_missing_`__…_(d8a02a0fcbb390a3).snap} | 2 +- ..._-_Custom_type_with_pos…_(a028edbafe180ca).snap} | 4 +- ..._Cover_non-keyword_re…_(707b284610419a54).snap | 20 +++---- .../mdtest/statically_known_branches.md | 2 +- .../resources/mdtest/subscript/instance.md | 8 ++- .../resources/mdtest/unreachable.md | 2 +- .../resources/mdtest/with/async.md | 4 +- .../resources/mdtest/with/sync.md | 4 +- crates/ty_python_semantic/src/types.rs | 22 ++++---- .../ty_python_semantic/src/types/call/bind.rs | 2 +- .../src/types/diagnostic.rs | 38 +++++++------- .../src/types/infer/builder.rs | 35 ++++++------- .../e2e__commands__debug_command.snap | 6 +-- ty.schema.json | 18 +++---- 41 files changed, 213 insertions(+), 194 deletions(-) rename crates/ty_python_semantic/resources/mdtest/snapshots/{async_for.md_-_Async_-_Error_cases_-_Possibly_unbound_`__…_(42b1d61a2b7be1b5).snap => async_for.md_-_Async_-_Error_cases_-_Possibly_missing_`__…_(33924dbae5117216).snap} (94%) rename crates/ty_python_semantic/resources/mdtest/snapshots/{async_for.md_-_Async_-_Error_cases_-_Possibly_unbound_`__…_(74ad2f945cad6ed8).snap => async_for.md_-_Async_-_Error_cases_-_Possibly_missing_`__…_(e2600ca4708d9e54).snap} (94%) rename crates/ty_python_semantic/resources/mdtest/snapshots/{attribute_assignment…_-_Attribute_assignment_-_Possibly-unbound_att…_(e5bdf78c427cb7fc).snap => attribute_assignment…_-_Attribute_assignment_-_Possibly-missing_att…_(e603e3da35f55c73).snap} (52%) rename crates/ty_python_semantic/resources/mdtest/snapshots/{for.md_-_For_loops_-_Possibly_unbound_`__…_(b1ce0da35c06026).snap => for.md_-_For_loops_-_Possibly_missing_`__…_(77269542b8e81774).snap} (98%) rename crates/ty_python_semantic/resources/mdtest/snapshots/{for.md_-_For_loops_-_Possibly_unbound_`__…_(3b75cc467e6e012).snap => for.md_-_For_loops_-_Possibly_missing_`__…_(9f781babda99d74b).snap} (96%) rename crates/ty_python_semantic/resources/mdtest/snapshots/{for.md_-_For_loops_-_Possibly_unbound_`__…_(8745233539d31200).snap => for.md_-_For_loops_-_Possibly_missing_`__…_(d8a02a0fcbb390a3).snap} (93%) rename crates/ty_python_semantic/resources/mdtest/snapshots/{invalid_await.md_-_Invalid_await_diagno…_-_Custom_type_with_pos…_(e3444b7a7f960d04).snap => invalid_await.md_-_Invalid_await_diagno…_-_Custom_type_with_pos…_(a028edbafe180ca).snap} (92%) diff --git a/crates/ty/docs/environment.md b/crates/ty/docs/environment.md index c4552f2e58..9b9061518f 100644 --- a/crates/ty/docs/environment.md +++ b/crates/ty/docs/environment.md @@ -42,6 +42,13 @@ Used to determine if an active Conda environment is the base environment or not. Used to detect an activated Conda environment location. If both `VIRTUAL_ENV` and `CONDA_PREFIX` are present, `VIRTUAL_ENV` will be preferred. +### `PYTHONPATH` + +Adds additional directories to ty's search paths. +The format is the same as the shell’s PATH: +one or more directory pathnames separated by os appropriate pathsep +(e.g. colons on Unix or semicolons on Windows). + ### `RAYON_NUM_THREADS` Specifies an upper limit for the number of threads ty uses when performing work in parallel. diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md index d2360c1112..3371905cfe 100644 --- a/crates/ty/docs/rules.md +++ b/crates/ty/docs/rules.md @@ -1863,21 +1863,21 @@ Use instead: a = 20 / 0 # type: ignore ``` -## `possibly-unbound-attribute` +## `possibly-missing-attribute` Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · -[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute) · +[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-attribute) · [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1328) **What it does** -Checks for possibly unbound attributes. +Checks for possibly missing attributes. **Why is this bad?** -Attempting to access an unbound attribute will raise an `AttributeError` at runtime. +Attempting to access a missing attribute will raise an `AttributeError` at runtime. **Examples** @@ -1889,23 +1889,23 @@ class A: A.c # AttributeError: type object 'A' has no attribute 'c' ``` -## `possibly-unbound-implicit-call` +## `possibly-missing-implicit-call` Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · -[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call) · +[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-implicit-call) · [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L132) **What it does** -Checks for implicit calls to possibly unbound methods. +Checks for implicit calls to possibly missing methods. **Why is this bad?** Expressions such as `x[y]` and `x * y` call methods under the hood (`__getitem__` and `__mul__` respectively). -Calling an unbound method will raise an `AttributeError` at runtime. +Calling a missing method will raise an `AttributeError` at runtime. **Examples** @@ -1919,21 +1919,21 @@ class A: A()[0] # TypeError: 'A' object is not subscriptable ``` -## `possibly-unbound-import` +## `possibly-missing-import` Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · -[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import) · +[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-import) · [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1350) **What it does** -Checks for imports of symbols that may be unbound. +Checks for imports of symbols that may be missing. **Why is this bad?** -Importing an unbound module or name will raise a `ModuleNotFoundError` +Importing a missing module or name will raise a `ModuleNotFoundError` or `ImportError` at runtime. **Examples** diff --git a/crates/ty_python_semantic/resources/mdtest/attributes.md b/crates/ty_python_semantic/resources/mdtest/attributes.md index 0e49bf490d..8f5742bcc7 100644 --- a/crates/ty_python_semantic/resources/mdtest/attributes.md +++ b/crates/ty_python_semantic/resources/mdtest/attributes.md @@ -914,7 +914,7 @@ def _(flag: bool): reveal_type(C3.attr2) # revealed: Literal["metaclass value", "class value"] ``` -If the *metaclass* attribute is only partially defined, we emit a `possibly-unbound-attribute` +If the *metaclass* attribute is only partially defined, we emit a `possibly-missing-attribute` diagnostic: ```py @@ -924,12 +924,12 @@ def _(flag: bool): attr1: str = "metaclass value" class C4(metaclass=Meta4): ... - # error: [possibly-unbound-attribute] + # error: [possibly-missing-attribute] reveal_type(C4.attr1) # revealed: str ``` Finally, if both the metaclass attribute and the class-level attribute are only partially defined, -we union them and emit a `possibly-unbound-attribute` diagnostic: +we union them and emit a `possibly-missing-attribute` diagnostic: ```py def _(flag1: bool, flag2: bool): @@ -941,7 +941,7 @@ def _(flag1: bool, flag2: bool): if flag2: attr1 = "class value" - # error: [possibly-unbound-attribute] + # error: [possibly-missing-attribute] reveal_type(C5.attr1) # revealed: Unknown | Literal["metaclass value", "class value"] ``` @@ -1180,13 +1180,13 @@ def _(flag1: bool, flag2: bool): C = C1 if flag1 else C2 if flag2 else C3 - # error: [possibly-unbound-attribute] "Attribute `x` on type ` | | ` is possibly unbound" + # error: [possibly-missing-attribute] "Attribute `x` on type ` | | ` may be missing" 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-unbound-attribute] "Attribute `x` on type `C1 | C2 | C3` is possibly unbound" + # error: [possibly-missing-attribute] "Attribute `x` on type `C1 | C2 | C3` may be missing" 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`" @@ -1212,18 +1212,18 @@ def _(flag: bool, flag1: bool, flag2: bool): C = C1 if flag1 else C2 if flag2 else C3 - # error: [possibly-unbound-attribute] "Attribute `x` on type ` | | ` is possibly unbound" + # error: [possibly-missing-attribute] "Attribute `x` on type ` | | ` may be missing" reveal_type(C.x) # revealed: Unknown | Literal[1, 2, 3] - # error: [possibly-unbound-attribute] + # error: [possibly-missing-attribute] C.x = 100 - # Note: we might want to consider ignoring possibly-unbound diagnostics for instance attributes eventually, + # 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-unbound-attribute] "Attribute `x` on type `C1 | C2 | C3` is possibly unbound" + # error: [possibly-missing-attribute] "Attribute `x` on type `C1 | C2 | C3` may be missing" reveal_type(C().x) # revealed: Unknown | Literal[1, 2, 3] - # error: [possibly-unbound-attribute] + # error: [possibly-missing-attribute] C().x = 100 ``` @@ -1287,16 +1287,16 @@ def _(flag: bool): if flag: x = 2 - # error: [possibly-unbound-attribute] + # error: [possibly-missing-attribute] reveal_type(Bar.x) # revealed: Unknown | Literal[2, 1] - # error: [possibly-unbound-attribute] + # error: [possibly-missing-attribute] Bar.x = 3 - # error: [possibly-unbound-attribute] + # error: [possibly-missing-attribute] reveal_type(Bar().x) # revealed: Unknown | Literal[2, 1] - # error: [possibly-unbound-attribute] + # error: [possibly-missing-attribute] Bar().x = 3 ``` @@ -1304,7 +1304,7 @@ def _(flag: bool): We currently treat implicit instance attributes to be bound, even if they are only conditionally defined within a method. If the class-level definition or the whole method is only conditionally -available, we emit a `possibly-unbound-attribute` diagnostic. +available, we emit a `possibly-missing-attribute` diagnostic. #### Possibly unbound and undeclared @@ -1484,17 +1484,17 @@ def _(flag: bool): class B1: ... def inner1(a_and_b: Intersection[A1, B1]): - # error: [possibly-unbound-attribute] + # error: [possibly-missing-attribute] reveal_type(a_and_b.x) # revealed: P - # error: [possibly-unbound-attribute] + # error: [possibly-missing-attribute] a_and_b.x = R() # Same for class objects def inner1_class(a_and_b: Intersection[type[A1], type[B1]]): - # error: [possibly-unbound-attribute] + # error: [possibly-missing-attribute] reveal_type(a_and_b.x) # revealed: P - # error: [possibly-unbound-attribute] + # error: [possibly-missing-attribute] a_and_b.x = R() class A2: @@ -1509,7 +1509,7 @@ def _(flag: bool): # TODO: this should not be an error, we need better intersection # handling in `validate_attribute_assignment` for this - # error: [possibly-unbound-attribute] + # error: [possibly-missing-attribute] a_and_b.x = R() # Same for class objects def inner2_class(a_and_b: Intersection[type[A2], type[B1]]): @@ -1524,17 +1524,17 @@ def _(flag: bool): x: Q = Q() def inner3(a_and_b: Intersection[A3, B3]): - # error: [possibly-unbound-attribute] + # error: [possibly-missing-attribute] reveal_type(a_and_b.x) # revealed: P & Q - # error: [possibly-unbound-attribute] + # error: [possibly-missing-attribute] a_and_b.x = R() # Same for class objects def inner3_class(a_and_b: Intersection[type[A3], type[B3]]): - # error: [possibly-unbound-attribute] + # error: [possibly-missing-attribute] reveal_type(a_and_b.x) # revealed: P & Q - # error: [possibly-unbound-attribute] + # error: [possibly-missing-attribute] a_and_b.x = R() class A4: ... @@ -1649,7 +1649,7 @@ If an attribute is defined on the class, it takes precedence over the `__getattr reveal_type(c.class_attr) # revealed: int ``` -If the class attribute is possibly unbound, we union the type of the attribute with the fallback +If the class attribute is possibly missing, we union the type of the attribute with the fallback type of the `__getattr__` method: ```py diff --git a/crates/ty_python_semantic/resources/mdtest/boundness_declaredness/public.md b/crates/ty_python_semantic/resources/mdtest/boundness_declaredness/public.md index ab4f3ff0de..5dc20fa1d4 100644 --- a/crates/ty_python_semantic/resources/mdtest/boundness_declaredness/public.md +++ b/crates/ty_python_semantic/resources/mdtest/boundness_declaredness/public.md @@ -26,7 +26,7 @@ In particular, we should raise errors in the "possibly-undeclared-and-unbound" a | **Diagnostic** | declared | possibly-undeclared | undeclared | | ---------------- | -------- | ------------------------- | ------------------- | | bound | | | | -| possibly-unbound | | `possibly-unbound-import` | ? | +| possibly-unbound | | `possibly-missing-import` | ? | | unbound | | ? | `unresolved-import` | ## Declared @@ -158,7 +158,7 @@ a = None If a symbol is possibly undeclared and possibly unbound, we also use the union of the declared and inferred types. This case is interesting because the "possibly declared" definition might not be the -same as the "possibly bound" definition (symbol `b`). Note that we raise a `possibly-unbound-import` +same as the "possibly bound" definition (symbol `b`). Note that we raise a `possibly-missing-import` error for both `a` and `b`: `mod.py`: @@ -177,8 +177,8 @@ else: ``` ```py -# error: [possibly-unbound-import] -# error: [possibly-unbound-import] +# error: [possibly-missing-import] "Member `a` of module `mod` may be missing" +# error: [possibly-missing-import] "Member `b` of module `mod` may be missing" from mod import a, b reveal_type(a) # revealed: Literal[1] | Any @@ -332,8 +332,8 @@ if flag(): ``` ```py -# error: [possibly-unbound-import] -# error: [possibly-unbound-import] +# error: [possibly-missing-import] +# error: [possibly-missing-import] from mod import MyInt, C reveal_type(MyInt) # revealed: diff --git a/crates/ty_python_semantic/resources/mdtest/call/callable_instance.md b/crates/ty_python_semantic/resources/mdtest/call/callable_instance.md index d6e36e061f..52f61bb5ed 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/callable_instance.md +++ b/crates/ty_python_semantic/resources/mdtest/call/callable_instance.md @@ -19,7 +19,7 @@ b = Unit()(3.0) # error: "Object of type `Unit` is not callable" reveal_type(b) # revealed: Unknown ``` -## Possibly unbound `__call__` method +## Possibly missing `__call__` method ```py def _(flag: bool): @@ -29,7 +29,7 @@ def _(flag: bool): return 1 a = PossiblyNotCallable() - result = a() # error: "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)" + result = a() # error: "Object of type `PossiblyNotCallable` is not callable (possibly missing `__call__` method)" reveal_type(result) # revealed: int ``` @@ -105,7 +105,7 @@ reveal_type(c()) # revealed: int ## Union over callables -### Possibly unbound `__call__` +### Possibly missing `__call__` ```py def outer(cond1: bool): @@ -122,6 +122,6 @@ def outer(cond1: bool): else: a = Other() - # error: [call-non-callable] "Object of type `Test` is not callable (possibly unbound `__call__` method)" + # error: [call-non-callable] "Object of type `Test` is not callable (possibly missing `__call__` method)" a() ``` diff --git a/crates/ty_python_semantic/resources/mdtest/call/constructor.md b/crates/ty_python_semantic/resources/mdtest/call/constructor.md index c8bdd664cd..b6e65fcdf2 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/constructor.md +++ b/crates/ty_python_semantic/resources/mdtest/call/constructor.md @@ -158,15 +158,15 @@ def _(flag: bool) -> None: def __new__(cls): return object.__new__(cls) - # error: [possibly-unbound-implicit-call] + # error: [possibly-missing-implicit-call] reveal_type(Foo()) # revealed: Foo - # error: [possibly-unbound-implicit-call] + # error: [possibly-missing-implicit-call] # error: [too-many-positional-arguments] reveal_type(Foo(1)) # revealed: Foo ``` -#### Possibly unbound `__call__` on `__new__` callable +#### Possibly missing `__call__` on `__new__` callable ```py def _(flag: bool) -> None: @@ -178,11 +178,11 @@ def _(flag: bool) -> None: class Foo: __new__ = Callable() - # error: [call-non-callable] "Object of type `Callable` is not callable (possibly unbound `__call__` method)" + # error: [call-non-callable] "Object of type `Callable` is not callable (possibly missing `__call__` method)" reveal_type(Foo(1)) # revealed: Foo # TODO should be - error: [missing-argument] "No argument provided for required parameter `x` of bound method `__call__`" # but we currently infer the signature of `__call__` as unknown, so it accepts any arguments - # error: [call-non-callable] "Object of type `Callable` is not callable (possibly unbound `__call__` method)" + # error: [call-non-callable] "Object of type `Callable` is not callable (possibly missing `__call__` method)" reveal_type(Foo()) # revealed: Foo ``` @@ -294,11 +294,11 @@ def _(flag: bool) -> None: class Foo: __init__ = Callable() - # error: [call-non-callable] "Object of type `Callable` is not callable (possibly unbound `__call__` method)" + # error: [call-non-callable] "Object of type `Callable` is not callable (possibly missing `__call__` method)" reveal_type(Foo(1)) # revealed: Foo # TODO should be - error: [missing-argument] "No argument provided for required parameter `x` of bound method `__call__`" # but we currently infer the signature of `__call__` as unknown, so it accepts any arguments - # error: [call-non-callable] "Object of type `Callable` is not callable (possibly unbound `__call__` method)" + # error: [call-non-callable] "Object of type `Callable` is not callable (possibly missing `__call__` method)" reveal_type(Foo()) # revealed: Foo ``` diff --git a/crates/ty_python_semantic/resources/mdtest/call/dunder.md b/crates/ty_python_semantic/resources/mdtest/call/dunder.md index 7eadb96dae..09b83f0035 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/dunder.md +++ b/crates/ty_python_semantic/resources/mdtest/call/dunder.md @@ -114,7 +114,11 @@ def _(flag: bool): this_fails = ThisFails() - # error: [possibly-unbound-implicit-call] + # TODO: this would be a friendlier diagnostic if we propagated the error up the stack + # and transformed it into a `[not-subscriptable]` error with a subdiagnostic explaining + # that the cause of the error was a possibly missing `__getitem__` method + # + # error: [possibly-missing-implicit-call] "Method `__getitem__` of type `ThisFails` may be missing" reveal_type(this_fails[0]) # revealed: Unknown | str ``` @@ -270,6 +274,11 @@ def _(flag: bool): return str(key) c = C() - # error: [possibly-unbound-implicit-call] + + # TODO: this would be a friendlier diagnostic if we propagated the error up the stack + # and transformed it into a `[not-subscriptable]` error with a subdiagnostic explaining + # that the cause of the error was a possibly missing `__getitem__` method + # + # error: [possibly-missing-implicit-call] "Method `__getitem__` of type `C` may be missing" reveal_type(c[0]) # revealed: str ``` diff --git a/crates/ty_python_semantic/resources/mdtest/call/methods.md b/crates/ty_python_semantic/resources/mdtest/call/methods.md index 02893d9a24..68c2175e6f 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/methods.md +++ b/crates/ty_python_semantic/resources/mdtest/call/methods.md @@ -325,7 +325,7 @@ class D(metaclass=Meta): reveal_type(D.f(1)) # revealed: Literal["a"] ``` -If the class method is possibly unbound, we union the return types: +If the class method is possibly missing, we union the return types: ```py def flag() -> bool: diff --git a/crates/ty_python_semantic/resources/mdtest/class/super.md b/crates/ty_python_semantic/resources/mdtest/class/super.md index c7b6e0ef80..b4befc0e44 100644 --- a/crates/ty_python_semantic/resources/mdtest/class/super.md +++ b/crates/ty_python_semantic/resources/mdtest/class/super.md @@ -219,7 +219,7 @@ def f(x: C | D): s = super(A, x) reveal_type(s) # revealed: , C> | , D> - # error: [possibly-unbound-attribute] "Attribute `b` on type `, C> | , D>` is possibly unbound" + # error: [possibly-missing-attribute] "Attribute `b` on type `, C> | , D>` may be missing" s.b def f(flag: bool): @@ -259,7 +259,7 @@ def f(flag: bool): reveal_type(s.x) # revealed: Unknown | Literal[1, 2] reveal_type(s.y) # revealed: int | str - # error: [possibly-unbound-attribute] "Attribute `a` on type `, B> | , D>` is possibly unbound" + # error: [possibly-missing-attribute] "Attribute `a` on type `, B> | , D>` may be missing" reveal_type(s.a) # revealed: str ``` diff --git a/crates/ty_python_semantic/resources/mdtest/descriptor_protocol.md b/crates/ty_python_semantic/resources/mdtest/descriptor_protocol.md index 04b2d9209c..ed80839a76 100644 --- a/crates/ty_python_semantic/resources/mdtest/descriptor_protocol.md +++ b/crates/ty_python_semantic/resources/mdtest/descriptor_protocol.md @@ -351,7 +351,7 @@ reveal_type(C4.meta_attribute) # revealed: Literal["value on metaclass"] reveal_type(C4.meta_non_data_descriptor) # revealed: Literal["non-data"] ``` -When a metaclass data descriptor is possibly unbound, we union the result type of its `__get__` +When a metaclass data descriptor is possibly missing, we union the result type of its `__get__` method with an underlying class level attribute, if present: ```py @@ -365,7 +365,7 @@ def _(flag: bool): meta_data_descriptor1: Literal["value on class"] = "value on class" reveal_type(C5.meta_data_descriptor1) # revealed: Literal["data", "value on class"] - # error: [possibly-unbound-attribute] + # error: [possibly-missing-attribute] reveal_type(C5.meta_data_descriptor2) # revealed: Literal["data"] # TODO: We currently emit two diagnostics here, corresponding to the two states of `flag`. The diagnostics are not @@ -375,11 +375,11 @@ def _(flag: bool): # error: [invalid-assignment] "Object of type `None` is not assignable to attribute `meta_data_descriptor1` of type `Literal["value on class"]`" C5.meta_data_descriptor1 = None - # error: [possibly-unbound-attribute] + # error: [possibly-missing-attribute] C5.meta_data_descriptor2 = 1 ``` -When a class-level attribute is possibly unbound, we union its (descriptor protocol) type with the +When a class-level attribute is possibly missing, we union its (descriptor protocol) type with the metaclass attribute (unless it's a data descriptor, which always takes precedence): ```py @@ -401,7 +401,7 @@ def _(flag: bool): reveal_type(C6.attribute1) # revealed: Literal["data"] reveal_type(C6.attribute2) # revealed: Literal["non-data", "value on class"] reveal_type(C6.attribute3) # revealed: Literal["value on metaclass", "value on class"] - # error: [possibly-unbound-attribute] + # error: [possibly-missing-attribute] reveal_type(C6.attribute4) # revealed: Literal["value on class"] ``` @@ -756,16 +756,16 @@ def _(flag: bool): non_data: NonDataDescriptor = NonDataDescriptor() data: DataDescriptor = DataDescriptor() - # error: [possibly-unbound-attribute] "Attribute `non_data` on type `` is possibly unbound" + # error: [possibly-missing-attribute] "Attribute `non_data` on type `` may be missing" reveal_type(PossiblyUnbound.non_data) # revealed: int - # error: [possibly-unbound-attribute] "Attribute `non_data` on type `PossiblyUnbound` is possibly unbound" + # error: [possibly-missing-attribute] "Attribute `non_data` on type `PossiblyUnbound` may be missing" reveal_type(PossiblyUnbound().non_data) # revealed: int - # error: [possibly-unbound-attribute] "Attribute `data` on type `` is possibly unbound" + # error: [possibly-missing-attribute] "Attribute `data` on type `` may be missing" reveal_type(PossiblyUnbound.data) # revealed: int - # error: [possibly-unbound-attribute] "Attribute `data` on type `PossiblyUnbound` is possibly unbound" + # error: [possibly-missing-attribute] "Attribute `data` on type `PossiblyUnbound` may be missing" reveal_type(PossiblyUnbound().data) # revealed: int ``` diff --git a/crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_assignment.md b/crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_assignment.md index 83db02c2be..61816ff56f 100644 --- a/crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_assignment.md +++ b/crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_assignment.md @@ -69,7 +69,7 @@ instance = C() instance.non_existent = 1 # error: [unresolved-attribute] ``` -## Possibly-unbound attributes +## Possibly-missing attributes When trying to set an attribute that is not defined in all branches, we emit errors: @@ -79,10 +79,10 @@ def _(flag: bool) -> None: if flag: attr: int = 0 - C.attr = 1 # error: [possibly-unbound-attribute] + C.attr = 1 # error: [possibly-missing-attribute] instance = C() - instance.attr = 1 # error: [possibly-unbound-attribute] + instance.attr = 1 # error: [possibly-missing-attribute] ``` ## Data descriptors diff --git a/crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_await.md b/crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_await.md index 60d3439366..24f5f98c49 100644 --- a/crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_await.md +++ b/crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_await.md @@ -23,7 +23,7 @@ async def main() -> None: await MissingAwait() # error: [invalid-await] ``` -## Custom type with possibly unbound `__await__` +## Custom type with possibly missing `__await__` This diagnostic also points to the method definition if available. diff --git a/crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.md b/crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.md index c6f8cd803a..31cafa14bf 100644 --- a/crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.md +++ b/crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.md @@ -116,7 +116,7 @@ def _(n: int): # error: [invalid-argument-type] "Argument to function `f5` is incorrect: Expected `str`, found `Literal[3]`" # error: [no-matching-overload] "No overload of function `f6` matches arguments" # error: [call-non-callable] "Object of type `Literal[5]` is not callable" - # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)" + # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly missing `__call__` method)" x = f(3) ``` diff --git a/crates/ty_python_semantic/resources/mdtest/expression/attribute.md b/crates/ty_python_semantic/resources/mdtest/expression/attribute.md index 43df56264c..f59da0bc48 100644 --- a/crates/ty_python_semantic/resources/mdtest/expression/attribute.md +++ b/crates/ty_python_semantic/resources/mdtest/expression/attribute.md @@ -26,7 +26,7 @@ def _(flag: bool): reveal_type(A.union_declared) # revealed: int | str - # error: [possibly-unbound-attribute] "Attribute `possibly_unbound` on type `` is possibly unbound" + # error: [possibly-missing-attribute] "Attribute `possibly_unbound` on type `` may be missing" reveal_type(A.possibly_unbound) # revealed: str # error: [unresolved-attribute] "Type `` has no attribute `non_existent`" diff --git a/crates/ty_python_semantic/resources/mdtest/import/conditional.md b/crates/ty_python_semantic/resources/mdtest/import/conditional.md index 703bf6078f..d2896ae2cd 100644 --- a/crates/ty_python_semantic/resources/mdtest/import/conditional.md +++ b/crates/ty_python_semantic/resources/mdtest/import/conditional.md @@ -22,7 +22,7 @@ reveal_type(y) ``` ```py -# error: [possibly-unbound-import] "Member `y` of module `maybe_unbound` is possibly unbound" +# error: [possibly-missing-import] "Member `y` of module `maybe_unbound` may be missing" from maybe_unbound import x, y reveal_type(x) # revealed: Unknown | Literal[3] @@ -53,7 +53,7 @@ reveal_type(y) Importing an annotated name prefers the declared type over the inferred type: ```py -# error: [possibly-unbound-import] "Member `y` of module `maybe_unbound_annotated` is possibly unbound" +# error: [possibly-missing-import] "Member `y` of module `maybe_unbound_annotated` may be missing" from maybe_unbound_annotated import x, y reveal_type(x) # revealed: Unknown | Literal[3] diff --git a/crates/ty_python_semantic/resources/mdtest/import/conventions.md b/crates/ty_python_semantic/resources/mdtest/import/conventions.md index c4950b5d1c..48d93515a8 100644 --- a/crates/ty_python_semantic/resources/mdtest/import/conventions.md +++ b/crates/ty_python_semantic/resources/mdtest/import/conventions.md @@ -306,7 +306,7 @@ The following scenarios are when a re-export happens conditionally in a stub fil ### Global import ```py -# error: "Member `Foo` of module `a` is possibly unbound" +# error: "Member `Foo` of module `a` may be missing" from a import Foo reveal_type(Foo) # revealed: str @@ -337,7 +337,7 @@ Here, both the branches of the condition are import statements where one of them the other does not. ```py -# error: "Member `Foo` of module `a` is possibly unbound" +# error: "Member `Foo` of module `a` may be missing" from a import Foo reveal_type(Foo) # revealed: @@ -365,7 +365,7 @@ class Foo: ... ### Re-export in one branch ```py -# error: "Member `Foo` of module `a` is possibly unbound" +# error: "Member `Foo` of module `a` may be missing" from a import Foo reveal_type(Foo) # revealed: diff --git a/crates/ty_python_semantic/resources/mdtest/loops/async_for.md b/crates/ty_python_semantic/resources/mdtest/loops/async_for.md index 25eee5c071..1271e2b41f 100644 --- a/crates/ty_python_semantic/resources/mdtest/loops/async_for.md +++ b/crates/ty_python_semantic/resources/mdtest/loops/async_for.md @@ -88,7 +88,7 @@ async def foo(): reveal_type(x) # revealed: Unknown ``` -### Possibly unbound `__anext__` method +### Possibly missing `__anext__` method ```py from typing_extensions import reveal_type @@ -108,7 +108,7 @@ async def foo(flag: bool): reveal_type(x) # revealed: int ``` -### Possibly unbound `__aiter__` method +### Possibly missing `__aiter__` method ```py from typing_extensions import reveal_type diff --git a/crates/ty_python_semantic/resources/mdtest/loops/for.md b/crates/ty_python_semantic/resources/mdtest/loops/for.md index 9cc073b91e..b0433156a3 100644 --- a/crates/ty_python_semantic/resources/mdtest/loops/for.md +++ b/crates/ty_python_semantic/resources/mdtest/loops/for.md @@ -363,7 +363,7 @@ for x in Bad(): reveal_type(x) # revealed: Unknown ``` -## `__iter__` returns an object with a possibly unbound `__next__` method +## `__iter__` returns an object with a possibly missing `__next__` method ```py def _(flag: bool): @@ -412,7 +412,7 @@ for y in Iterable2(): reveal_type(y) # revealed: Unknown ``` -## Possibly unbound `__iter__` and bad `__getitem__` method +## Possibly missing `__iter__` and bad `__getitem__` method @@ -438,12 +438,12 @@ def _(flag: bool): reveal_type(x) # revealed: int | bytes ``` -## Possibly unbound `__iter__` and not-callable `__getitem__` +## Possibly missing `__iter__` and not-callable `__getitem__` This snippet tests that we infer the element type correctly in the following edge case: - `__iter__` is a method with the correct parameter spec that returns a valid iterator; BUT -- `__iter__` is possibly unbound; AND +- `__iter__` is possibly missing; AND - `__getitem__` is set to a non-callable type It's important that we emit a diagnostic here, but it's also important that we still use the return @@ -466,7 +466,7 @@ def _(flag: bool): reveal_type(x) # revealed: int ``` -## Possibly unbound `__iter__` and possibly unbound `__getitem__` +## Possibly missing `__iter__` and possibly missing `__getitem__` @@ -560,7 +560,7 @@ for x in Iterable(): reveal_type(x) # revealed: int ``` -## Possibly unbound `__iter__` but definitely bound `__getitem__` +## Possibly missing `__iter__` but definitely bound `__getitem__` Here, we should not emit a diagnostic: if `__iter__` is unbound, we should fallback to `__getitem__`: @@ -694,7 +694,7 @@ def _(flag: bool): reveal_type(y) # revealed: str | int ``` -## Possibly unbound `__iter__` and possibly invalid `__getitem__` +## Possibly missing `__iter__` and possibly invalid `__getitem__` diff --git a/crates/ty_python_semantic/resources/mdtest/narrow/assignment.md b/crates/ty_python_semantic/resources/mdtest/narrow/assignment.md index 18dc4242a5..bc2def2752 100644 --- a/crates/ty_python_semantic/resources/mdtest/narrow/assignment.md +++ b/crates/ty_python_semantic/resources/mdtest/narrow/assignment.md @@ -135,9 +135,9 @@ a.b = B() reveal_type(a.b) # revealed: B reveal_type(a.b.c1) # revealed: C | None reveal_type(a.b.c2) # revealed: C | None -# error: [possibly-unbound-attribute] +# error: [possibly-missing-attribute] reveal_type(a.b.c1.d) # revealed: D | None -# error: [possibly-unbound-attribute] +# error: [possibly-missing-attribute] reveal_type(a.b.c2.d) # revealed: D | None ``` @@ -295,9 +295,9 @@ class C: reveal_type(b.a.x[0]) # revealed: Literal[0] def _(): - # error: [possibly-unbound-attribute] + # error: [possibly-missing-attribute] reveal_type(b.a.x[0]) # revealed: Unknown | int | None - # error: [possibly-unbound-attribute] + # error: [possibly-missing-attribute] reveal_type(b.a.x) # revealed: Unknown | list[int | None] reveal_type(b.a) # revealed: Unknown | A | None ``` diff --git a/crates/ty_python_semantic/resources/mdtest/narrow/conditionals/nested.md b/crates/ty_python_semantic/resources/mdtest/narrow/conditionals/nested.md index 52750aec89..dd10ff9585 100644 --- a/crates/ty_python_semantic/resources/mdtest/narrow/conditionals/nested.md +++ b/crates/ty_python_semantic/resources/mdtest/narrow/conditionals/nested.md @@ -161,7 +161,7 @@ class _: a.b = B() class _: - # error: [possibly-unbound-attribute] + # error: [possibly-missing-attribute] reveal_type(a.b.c1.d) # revealed: D | None reveal_type(a.b.c1) # revealed: C | None ``` diff --git a/crates/ty_python_semantic/resources/mdtest/narrow/hasattr.md b/crates/ty_python_semantic/resources/mdtest/narrow/hasattr.md index 02e07d80c2..ab38563ee7 100644 --- a/crates/ty_python_semantic/resources/mdtest/narrow/hasattr.md +++ b/crates/ty_python_semantic/resources/mdtest/narrow/hasattr.md @@ -60,7 +60,7 @@ def _(obj: WithSpam): ``` When a class may or may not have a `spam` attribute, `hasattr` narrowing can provide evidence that -the attribute exists. Here, no `possibly-unbound-attribute` error is emitted in the `if` branch: +the attribute exists. Here, no `possibly-missing-attribute` error is emitted in the `if` branch: ```py def returns_bool() -> bool: @@ -71,7 +71,7 @@ class MaybeWithSpam: spam: int = 42 def _(obj: MaybeWithSpam): - # error: [possibly-unbound-attribute] + # error: [possibly-missing-attribute] reveal_type(obj.spam) # revealed: int if hasattr(obj, "spam"): @@ -81,7 +81,7 @@ def _(obj: MaybeWithSpam): reveal_type(obj) # revealed: MaybeWithSpam & ~ # TODO: Ideally, we would emit `[unresolved-attribute]` and reveal `Unknown` here: - # error: [possibly-unbound-attribute] + # error: [possibly-missing-attribute] reveal_type(obj.spam) # revealed: int ``` diff --git a/crates/ty_python_semantic/resources/mdtest/scopes/unbound.md b/crates/ty_python_semantic/resources/mdtest/scopes/unbound.md index 8729012093..43c8f5f564 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-unbound-attribute] "Attribute `x` on type `` is possibly unbound" +# error: [possibly-missing-attribute] "Attribute `x` on type `` may be missing" reveal_type(C.x) # revealed: Unknown | Literal[2] reveal_type(C.y) # revealed: Unknown | Literal[1] ``` @@ -52,7 +52,7 @@ class C: elif coinflip(): x: str = "abc" -# error: [possibly-unbound-attribute] +# error: [possibly-missing-attribute] reveal_type(C.x) # revealed: int | str ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/async_for.md_-_Async_-_Error_cases_-_Possibly_unbound_`__…_(42b1d61a2b7be1b5).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/async_for.md_-_Async_-_Error_cases_-_Possibly_missing_`__…_(33924dbae5117216).snap similarity index 94% rename from crates/ty_python_semantic/resources/mdtest/snapshots/async_for.md_-_Async_-_Error_cases_-_Possibly_unbound_`__…_(42b1d61a2b7be1b5).snap rename to crates/ty_python_semantic/resources/mdtest/snapshots/async_for.md_-_Async_-_Error_cases_-_Possibly_missing_`__…_(33924dbae5117216).snap index c41a49507e..ddc15768bc 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/async_for.md_-_Async_-_Error_cases_-_Possibly_unbound_`__…_(42b1d61a2b7be1b5).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/async_for.md_-_Async_-_Error_cases_-_Possibly_missing_`__…_(33924dbae5117216).snap @@ -3,7 +3,7 @@ source: crates/ty_test/src/lib.rs expression: snapshot --- --- -mdtest name: async_for.md - Async - Error cases - Possibly unbound `__aiter__` method +mdtest name: async_for.md - Async - Error cases - Possibly missing `__aiter__` method mdtest path: crates/ty_python_semantic/resources/mdtest/loops/async_for.md --- diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/async_for.md_-_Async_-_Error_cases_-_Possibly_unbound_`__…_(74ad2f945cad6ed8).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/async_for.md_-_Async_-_Error_cases_-_Possibly_missing_`__…_(e2600ca4708d9e54).snap similarity index 94% rename from crates/ty_python_semantic/resources/mdtest/snapshots/async_for.md_-_Async_-_Error_cases_-_Possibly_unbound_`__…_(74ad2f945cad6ed8).snap rename to crates/ty_python_semantic/resources/mdtest/snapshots/async_for.md_-_Async_-_Error_cases_-_Possibly_missing_`__…_(e2600ca4708d9e54).snap index 4060670bd9..f6e3888116 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/async_for.md_-_Async_-_Error_cases_-_Possibly_unbound_`__…_(74ad2f945cad6ed8).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/async_for.md_-_Async_-_Error_cases_-_Possibly_missing_`__…_(e2600ca4708d9e54).snap @@ -3,7 +3,7 @@ source: crates/ty_test/src/lib.rs expression: snapshot --- --- -mdtest name: async_for.md - Async - Error cases - Possibly unbound `__anext__` method +mdtest name: async_for.md - Async - Error cases - Possibly missing `__anext__` method mdtest path: crates/ty_python_semantic/resources/mdtest/loops/async_for.md --- diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment…_-_Attribute_assignment_-_Possibly-unbound_att…_(e5bdf78c427cb7fc).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment…_-_Attribute_assignment_-_Possibly-missing_att…_(e603e3da35f55c73).snap similarity index 52% rename from crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment…_-_Attribute_assignment_-_Possibly-unbound_att…_(e5bdf78c427cb7fc).snap rename to crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment…_-_Attribute_assignment_-_Possibly-missing_att…_(e603e3da35f55c73).snap index 369a67a256..9335fec6da 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment…_-_Attribute_assignment_-_Possibly-unbound_att…_(e5bdf78c427cb7fc).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/attribute_assignment…_-_Attribute_assignment_-_Possibly-missing_att…_(e603e3da35f55c73).snap @@ -3,7 +3,7 @@ source: crates/ty_test/src/lib.rs expression: snapshot --- --- -mdtest name: attribute_assignment.md - Attribute assignment - Possibly-unbound attributes +mdtest name: attribute_assignment.md - Attribute assignment - Possibly-missing attributes mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_assignment.md --- @@ -17,37 +17,37 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_as 3 | if flag: 4 | attr: int = 0 5 | -6 | C.attr = 1 # error: [possibly-unbound-attribute] +6 | C.attr = 1 # error: [possibly-missing-attribute] 7 | 8 | instance = C() -9 | instance.attr = 1 # error: [possibly-unbound-attribute] +9 | instance.attr = 1 # error: [possibly-missing-attribute] ``` # Diagnostics ``` -warning[possibly-unbound-attribute]: Attribute `attr` on type `` is possibly unbound +warning[possibly-missing-attribute]: Attribute `attr` on type `` may be missing --> src/mdtest_snippet.py:6:5 | 4 | attr: int = 0 5 | -6 | C.attr = 1 # error: [possibly-unbound-attribute] +6 | C.attr = 1 # error: [possibly-missing-attribute] | ^^^^^^ 7 | 8 | instance = C() | -info: rule `possibly-unbound-attribute` is enabled by default +info: rule `possibly-missing-attribute` is enabled by default ``` ``` -warning[possibly-unbound-attribute]: Attribute `attr` on type `C` is possibly unbound +warning[possibly-missing-attribute]: Attribute `attr` on type `C` may be missing --> src/mdtest_snippet.py:9:5 | 8 | instance = C() -9 | instance.attr = 1 # error: [possibly-unbound-attribute] +9 | instance.attr = 1 # error: [possibly-missing-attribute] | ^^^^^^^^^^^^^ | -info: rule `possibly-unbound-attribute` is enabled by default +info: rule `possibly-missing-attribute` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__…_(b1ce0da35c06026).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_missing_`__…_(77269542b8e81774).snap similarity index 98% rename from crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__…_(b1ce0da35c06026).snap rename to crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_missing_`__…_(77269542b8e81774).snap index 9d09acef59..dde017803c 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__…_(b1ce0da35c06026).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_missing_`__…_(77269542b8e81774).snap @@ -3,7 +3,7 @@ source: crates/ty_test/src/lib.rs expression: snapshot --- --- -mdtest name: for.md - For loops - Possibly unbound `__iter__` and possibly invalid `__getitem__` +mdtest name: for.md - For loops - Possibly missing `__iter__` and possibly invalid `__getitem__` mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md --- diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__…_(3b75cc467e6e012).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_missing_`__…_(9f781babda99d74b).snap similarity index 96% rename from crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__…_(3b75cc467e6e012).snap rename to crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_missing_`__…_(9f781babda99d74b).snap index c7ec490902..b2bfc28b10 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__…_(3b75cc467e6e012).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_missing_`__…_(9f781babda99d74b).snap @@ -3,7 +3,7 @@ source: crates/ty_test/src/lib.rs expression: snapshot --- --- -mdtest name: for.md - For loops - Possibly unbound `__iter__` and bad `__getitem__` method +mdtest name: for.md - For loops - Possibly missing `__iter__` and bad `__getitem__` method mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md --- diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__…_(8745233539d31200).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_missing_`__…_(d8a02a0fcbb390a3).snap similarity index 93% rename from crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__…_(8745233539d31200).snap rename to crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_missing_`__…_(d8a02a0fcbb390a3).snap index 48013f5948..41e7b6a425 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_unbound_`__…_(8745233539d31200).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_missing_`__…_(d8a02a0fcbb390a3).snap @@ -3,7 +3,7 @@ source: crates/ty_test/src/lib.rs expression: snapshot --- --- -mdtest name: for.md - For loops - Possibly unbound `__iter__` and possibly unbound `__getitem__` +mdtest name: for.md - For loops - Possibly missing `__iter__` and possibly missing `__getitem__` mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md --- diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_Custom_type_with_pos…_(e3444b7a7f960d04).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_Custom_type_with_pos…_(a028edbafe180ca).snap similarity index 92% rename from crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_Custom_type_with_pos…_(e3444b7a7f960d04).snap rename to crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_Custom_type_with_pos…_(a028edbafe180ca).snap index 22233a6acd..ac576c9ad7 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_Custom_type_with_pos…_(e3444b7a7f960d04).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_Custom_type_with_pos…_(a028edbafe180ca).snap @@ -3,7 +3,7 @@ source: crates/ty_test/src/lib.rs expression: snapshot --- --- -mdtest name: invalid_await.md - Invalid await diagnostics - Custom type with possibly unbound `__await__` +mdtest name: invalid_await.md - Invalid await diagnostics - Custom type with possibly missing `__await__` mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_await.md --- @@ -41,7 +41,7 @@ error[invalid-await]: `PossiblyUnbound` is not awaitable | --------------- method defined here 6 | yield | -info: `__await__` is possibly unbound +info: `__await__` may be missing info: rule `invalid-await` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f…_-_Try_to_cover_all_pos…_-_Cover_non-keyword_re…_(707b284610419a54).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f…_-_Try_to_cover_all_pos…_-_Cover_non-keyword_re…_(707b284610419a54).snap index 86e63f2a35..929ad29c37 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f…_-_Try_to_cover_all_pos…_-_Cover_non-keyword_re…_(707b284610419a54).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f…_-_Try_to_cover_all_pos…_-_Cover_non-keyword_re…_(707b284610419a54).snap @@ -70,7 +70,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.m 56 | # error: [invalid-argument-type] "Argument to function `f5` is incorrect: Expected `str`, found `Literal[3]`" 57 | # error: [no-matching-overload] "No overload of function `f6` matches arguments" 58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable" -59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)" +59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly missing `__call__` method)" 60 | x = f(3) ``` @@ -81,7 +81,7 @@ error[call-non-callable]: Object of type `Literal[5]` is not callable --> src/mdtest_snippet.py:60:9 | 58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable" -59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)" +59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly missing `__call__` method)" 60 | x = f(3) | ^^^^ | @@ -92,11 +92,11 @@ info: rule `call-non-callable` is enabled by default ``` ``` -error[call-non-callable]: Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method) +error[call-non-callable]: Object of type `PossiblyNotCallable` is not callable (possibly missing `__call__` method) --> src/mdtest_snippet.py:60:9 | 58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable" -59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)" +59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly missing `__call__` method)" 60 | x = f(3) | ^^^^ | @@ -111,7 +111,7 @@ error[missing-argument]: No argument provided for required parameter `b` of func --> src/mdtest_snippet.py:60:9 | 58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable" -59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)" +59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly missing `__call__` method)" 60 | x = f(3) | ^^^^ | @@ -126,7 +126,7 @@ error[no-matching-overload]: No overload of function `f6` matches arguments --> src/mdtest_snippet.py:60:9 | 58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable" -59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)" +59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly missing `__call__` method)" 60 | x = f(3) | ^^^^ | @@ -162,7 +162,7 @@ error[invalid-argument-type]: Argument to function `f2` is incorrect --> src/mdtest_snippet.py:60:11 | 58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable" -59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)" +59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly missing `__call__` method)" 60 | x = f(3) | ^ Expected `str`, found `Literal[3]` | @@ -186,7 +186,7 @@ error[invalid-argument-type]: Argument to function `f4` is incorrect --> src/mdtest_snippet.py:60:11 | 58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable" -59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)" +59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly missing `__call__` method)" 60 | x = f(3) | ^ Argument type `Literal[3]` does not satisfy upper bound `str` of type variable `T` | @@ -210,7 +210,7 @@ error[invalid-argument-type]: Argument to function `f5` is incorrect --> src/mdtest_snippet.py:60:11 | 58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable" -59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)" +59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly missing `__call__` method)" 60 | x = f(3) | ^ Expected `str`, found `Literal[3]` | @@ -237,7 +237,7 @@ error[too-many-positional-arguments]: Too many positional arguments to function --> src/mdtest_snippet.py:60:11 | 58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable" -59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)" +59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly missing `__call__` method)" 60 | x = f(3) | ^ | diff --git a/crates/ty_python_semantic/resources/mdtest/statically_known_branches.md b/crates/ty_python_semantic/resources/mdtest/statically_known_branches.md index 3416d124e3..4b7e73498c 100644 --- a/crates/ty_python_semantic/resources/mdtest/statically_known_branches.md +++ b/crates/ty_python_semantic/resources/mdtest/statically_known_branches.md @@ -1530,7 +1530,7 @@ if flag(): ``` ```py -# error: [possibly-unbound-import] +# error: [possibly-missing-import] from module import symbol ``` diff --git a/crates/ty_python_semantic/resources/mdtest/subscript/instance.md b/crates/ty_python_semantic/resources/mdtest/subscript/instance.md index a7399d7152..7d1ad7f183 100644 --- a/crates/ty_python_semantic/resources/mdtest/subscript/instance.md +++ b/crates/ty_python_semantic/resources/mdtest/subscript/instance.md @@ -14,7 +14,11 @@ a = NotSubscriptable()[0] # error: "Cannot subscript object of type `NotSubscri class NotSubscriptable: __getitem__ = None -# error: "Method `__getitem__` of type `Unknown | None` is possibly not callable on object of type `NotSubscriptable`" +# TODO: this would be more user-friendly if the `call-non-callable` diagnostic was +# transformed into a `not-subscriptable` diagnostic with a subdiagnostic explaining +# that this was because `__getitem__` was possibly not callable +# +# error: [call-non-callable] "Method `__getitem__` of type `Unknown | None` may not be callable on object of type `NotSubscriptable`" a = NotSubscriptable()[0] ``` @@ -82,7 +86,7 @@ class NoSetitem: __setitem__ = None a = NoSetitem() -a[0] = 0 # error: "Method `__setitem__` of type `Unknown | None` is possibly not callable on object of type `NoSetitem`" +a[0] = 0 # error: "Method `__setitem__` of type `Unknown | None` may not be callable on object of type `NoSetitem`" ``` ## Valid `__setitem__` method diff --git a/crates/ty_python_semantic/resources/mdtest/unreachable.md b/crates/ty_python_semantic/resources/mdtest/unreachable.md index 4f05131763..7321ed9b01 100644 --- a/crates/ty_python_semantic/resources/mdtest/unreachable.md +++ b/crates/ty_python_semantic/resources/mdtest/unreachable.md @@ -198,7 +198,7 @@ import sys if sys.platform == "win32": # TODO: we should not emit an error here - # error: [possibly-unbound-attribute] + # error: [possibly-missing-attribute] sys.getwindowsversion() ``` diff --git a/crates/ty_python_semantic/resources/mdtest/with/async.md b/crates/ty_python_semantic/resources/mdtest/with/async.md index 2d907fa381..0c55e5087d 100644 --- a/crates/ty_python_semantic/resources/mdtest/with/async.md +++ b/crates/ty_python_semantic/resources/mdtest/with/async.md @@ -113,7 +113,7 @@ async def _(flag: bool): class NotAContextManager: ... context_expr = Manager1() if flag else NotAContextManager() - # error: [invalid-context-manager] "Object of type `Manager1 | NotAContextManager` cannot be used with `async with` because the methods `__aenter__` and `__aexit__` are possibly unbound" + # error: [invalid-context-manager] "Object of type `Manager1 | NotAContextManager` cannot be used with `async with` because the methods `__aenter__` and `__aexit__` are possibly missing" async with context_expr as f: reveal_type(f) # revealed: str ``` @@ -129,7 +129,7 @@ async def _(flag: bool): async def __exit__(self, *args): ... - # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `async with` because the method `__aenter__` is possibly unbound" + # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `async with` because the method `__aenter__` may be missing" async with Manager() as f: reveal_type(f) # revealed: CoroutineType[Any, Any, str] ``` diff --git a/crates/ty_python_semantic/resources/mdtest/with/sync.md b/crates/ty_python_semantic/resources/mdtest/with/sync.md index 1dd8d0ea70..15d6aec51e 100644 --- a/crates/ty_python_semantic/resources/mdtest/with/sync.md +++ b/crates/ty_python_semantic/resources/mdtest/with/sync.md @@ -113,7 +113,7 @@ def _(flag: bool): class NotAContextManager: ... context_expr = Manager1() if flag else NotAContextManager() - # error: [invalid-context-manager] "Object of type `Manager1 | NotAContextManager` cannot be used with `with` because the methods `__enter__` and `__exit__` are possibly unbound" + # error: [invalid-context-manager] "Object of type `Manager1 | NotAContextManager` cannot be used with `with` because the methods `__enter__` and `__exit__` are possibly missing" with context_expr as f: reveal_type(f) # revealed: str ``` @@ -129,7 +129,7 @@ def _(flag: bool): def __exit__(self, *args): ... - # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because the method `__enter__` is possibly unbound" + # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because the method `__enter__` may be missing" with Manager() as f: reveal_type(f) # revealed: str ``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 8096174af5..c2c11c5820 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -8,7 +8,7 @@ use bitflags::bitflags; use call::{CallDunderError, CallError, CallErrorKind}; use context::InferContext; use diagnostic::{ - INVALID_CONTEXT_MANAGER, INVALID_SUPER_ARGUMENT, NOT_ITERABLE, POSSIBLY_UNBOUND_IMPLICIT_CALL, + INVALID_CONTEXT_MANAGER, INVALID_SUPER_ARGUMENT, NOT_ITERABLE, POSSIBLY_MISSING_IMPLICIT_CALL, UNAVAILABLE_IMPLICIT_SUPER_ARGUMENTS, }; use ruff_db::diagnostic::{Annotation, Diagnostic, Span, SubDiagnostic, SubDiagnosticSeverity}; @@ -3830,7 +3830,7 @@ impl<'db> Type<'db> { }); } - // Don't trust possibly unbound `__bool__` method. + // Don't trust possibly missing `__bool__` method. Ok(Truthiness::Ambiguous) } @@ -8148,7 +8148,7 @@ impl<'db> AwaitError<'db> { } } Self::Call(CallDunderError::PossiblyUnbound(bindings)) => { - diag.info("`__await__` is possibly unbound"); + diag.info("`__await__` may be missing"); if let Some(definition_spans) = bindings.callable_type().function_spans(db) { diag.annotate( Annotation::secondary(definition_spans.signature) @@ -8255,7 +8255,7 @@ impl<'db> ContextManagerError<'db> { match call_dunder_error { CallDunderError::MethodNotAvailable => format!("it does not implement `{name}`"), CallDunderError::PossiblyUnbound(_) => { - format!("the method `{name}` is possibly unbound") + format!("the method `{name}` may be missing") } // TODO: Use more specific error messages for the different error cases. // E.g. hint toward the union variant that doesn't correctly implement enter, @@ -8272,7 +8272,7 @@ impl<'db> ContextManagerError<'db> { name_b: &str| { match (error_a, error_b) { (CallDunderError::PossiblyUnbound(_), CallDunderError::PossiblyUnbound(_)) => { - format!("the methods `{name_a}` and `{name_b}` are possibly unbound") + format!("the methods `{name_a}` and `{name_b}` are possibly missing") } (CallDunderError::MethodNotAvailable, CallDunderError::MethodNotAvailable) => { format!("it does not implement `{name_a}` and `{name_b}`") @@ -8821,7 +8821,7 @@ pub(super) enum BoolError<'db> { /// Any other reason why the type can't be converted to a bool. /// E.g. because calling `__bool__` returns in a union type and not all variants support `__bool__` or - /// because `__bool__` points to a type that has a possibly unbound `__call__` method. + /// because `__bool__` points to a type that has a possibly missing `__call__` method. Other { not_boolable_type: Type<'db> }, } @@ -8994,7 +8994,7 @@ impl<'db> ConstructorCallError<'db> { let report_init_error = |call_dunder_error: &CallDunderError<'db>| match call_dunder_error { CallDunderError::MethodNotAvailable => { if let Some(builder) = - context.report_lint(&POSSIBLY_UNBOUND_IMPLICIT_CALL, context_expression_node) + context.report_lint(&POSSIBLY_MISSING_IMPLICIT_CALL, context_expression_node) { // If we are using vendored typeshed, it should be impossible to have missing // or unbound `__init__` method on a class, as all classes have `object` in MRO. @@ -9008,10 +9008,10 @@ impl<'db> ConstructorCallError<'db> { } CallDunderError::PossiblyUnbound(bindings) => { if let Some(builder) = - context.report_lint(&POSSIBLY_UNBOUND_IMPLICIT_CALL, context_expression_node) + context.report_lint(&POSSIBLY_MISSING_IMPLICIT_CALL, context_expression_node) { builder.into_diagnostic(format_args!( - "Method `__init__` on type `{}` is possibly unbound.", + "Method `__init__` on type `{}` may be missing.", context_expression_type.display(context.db()), )); } @@ -9026,10 +9026,10 @@ impl<'db> ConstructorCallError<'db> { let report_new_error = |error: &DunderNewCallError<'db>| match error { DunderNewCallError::PossiblyUnbound(call_error) => { if let Some(builder) = - context.report_lint(&POSSIBLY_UNBOUND_IMPLICIT_CALL, context_expression_node) + context.report_lint(&POSSIBLY_MISSING_IMPLICIT_CALL, context_expression_node) { builder.into_diagnostic(format_args!( - "Method `__new__` on type `{}` is possibly unbound.", + "Method `__new__` on type `{}` may be missing.", context_expression_type.display(context.db()), )); } diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 714441c8ca..f5b2e4e349 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -1758,7 +1758,7 @@ impl<'db> CallableBinding<'db> { if self.dunder_call_is_possibly_unbound { if let Some(builder) = context.report_lint(&CALL_NON_CALLABLE, node) { let mut diag = builder.into_diagnostic(format_args!( - "Object of type `{}` is not callable (possibly unbound `__call__` method)", + "Object of type `{}` is not callable (possibly missing `__call__` method)", self.callable_type.display(context.db()), )); if let Some(union_diag) = union_diag { diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index 56ed2bf9d0..214eb30749 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -38,7 +38,7 @@ use std::fmt::Formatter; pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) { registry.register_lint(&AMBIGUOUS_PROTOCOL_MEMBER); registry.register_lint(&CALL_NON_CALLABLE); - registry.register_lint(&POSSIBLY_UNBOUND_IMPLICIT_CALL); + registry.register_lint(&POSSIBLY_MISSING_IMPLICIT_CALL); registry.register_lint(&CONFLICTING_ARGUMENT_FORMS); registry.register_lint(&CONFLICTING_DECLARATIONS); registry.register_lint(&CONFLICTING_METACLASS); @@ -80,8 +80,8 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) { registry.register_lint(&NOT_ITERABLE); registry.register_lint(&UNSUPPORTED_BOOL_CONVERSION); registry.register_lint(&PARAMETER_ALREADY_ASSIGNED); - registry.register_lint(&POSSIBLY_UNBOUND_ATTRIBUTE); - registry.register_lint(&POSSIBLY_UNBOUND_IMPORT); + registry.register_lint(&POSSIBLY_MISSING_ATTRIBUTE); + registry.register_lint(&POSSIBLY_MISSING_IMPORT); registry.register_lint(&POSSIBLY_UNRESOLVED_REFERENCE); registry.register_lint(&SUBCLASS_OF_FINAL_CLASS); registry.register_lint(&TYPE_ASSERTION_FAILURE); @@ -131,12 +131,12 @@ declare_lint! { declare_lint! { /// ## What it does - /// Checks for implicit calls to possibly unbound methods. + /// Checks for implicit calls to possibly missing methods. /// /// ## Why is this bad? /// Expressions such as `x[y]` and `x * y` call methods /// under the hood (`__getitem__` and `__mul__` respectively). - /// Calling an unbound method will raise an `AttributeError` at runtime. + /// Calling a missing method will raise an `AttributeError` at runtime. /// /// ## Examples /// ```python @@ -148,8 +148,8 @@ declare_lint! { /// /// A()[0] # TypeError: 'A' object is not subscriptable /// ``` - pub(crate) static POSSIBLY_UNBOUND_IMPLICIT_CALL = { - summary: "detects implicit calls to possibly unbound methods", + pub(crate) static POSSIBLY_MISSING_IMPLICIT_CALL = { + summary: "detects implicit calls to possibly missing methods", status: LintStatus::preview("1.0.0"), default_level: Level::Warn, } @@ -1327,10 +1327,10 @@ declare_lint! { declare_lint! { /// ## What it does - /// Checks for possibly unbound attributes. + /// Checks for possibly missing attributes. /// /// ## Why is this bad? - /// Attempting to access an unbound attribute will raise an `AttributeError` at runtime. + /// Attempting to access a missing attribute will raise an `AttributeError` at runtime. /// /// ## Examples /// ```python @@ -1340,8 +1340,8 @@ declare_lint! { /// /// A.c # AttributeError: type object 'A' has no attribute 'c' /// ``` - pub(crate) static POSSIBLY_UNBOUND_ATTRIBUTE = { - summary: "detects references to possibly unbound attributes", + pub(crate) static POSSIBLY_MISSING_ATTRIBUTE = { + summary: "detects references to possibly missing attributes", status: LintStatus::preview("1.0.0"), default_level: Level::Warn, } @@ -1349,10 +1349,10 @@ declare_lint! { declare_lint! { /// ## What it does - /// Checks for imports of symbols that may be unbound. + /// Checks for imports of symbols that may be missing. /// /// ## Why is this bad? - /// Importing an unbound module or name will raise a `ModuleNotFoundError` + /// Importing a missing module or name will raise a `ModuleNotFoundError` /// or `ImportError` at runtime. /// /// ## Examples @@ -1366,8 +1366,8 @@ declare_lint! { /// # main.py /// from module import a # ImportError: cannot import name 'a' from 'module' /// ``` - pub(crate) static POSSIBLY_UNBOUND_IMPORT = { - summary: "detects possibly unbound imports", + pub(crate) static POSSIBLY_MISSING_IMPORT = { + summary: "detects possibly missing imports", status: LintStatus::preview("1.0.0"), default_level: Level::Warn, } @@ -2197,17 +2197,17 @@ pub(super) fn report_possibly_unresolved_reference( builder.into_diagnostic(format_args!("Name `{id}` used when possibly not defined")); } -pub(super) fn report_possibly_unbound_attribute( +pub(super) fn report_possibly_missing_attribute( context: &InferContext, target: &ast::ExprAttribute, attribute: &str, object_ty: Type, ) { - let Some(builder) = context.report_lint(&POSSIBLY_UNBOUND_ATTRIBUTE, target) else { + let Some(builder) = context.report_lint(&POSSIBLY_MISSING_ATTRIBUTE, target) else { return; }; builder.into_diagnostic(format_args!( - "Attribute `{attribute}` on type `{}` is possibly unbound", + "Attribute `{attribute}` on type `{}` may be missing", object_ty.display(context.db()), )); } @@ -2793,7 +2793,7 @@ pub(crate) fn report_invalid_or_unsupported_base( CallDunderError::PossiblyUnbound(_) => { explain_mro_entries(&mut diagnostic); diagnostic.info(format_args!( - "Type `{}` has an `__mro_entries__` attribute, but it is possibly unbound", + "Type `{}` may have an `__mro_entries__` attribute, but it may be missing", base_type.display(db) )); } diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index db04778cf8..a276063415 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -52,7 +52,7 @@ use crate::types::diagnostic::{ INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, INVALID_DECLARATION, INVALID_GENERIC_CLASS, INVALID_KEY, INVALID_NAMED_TUPLE, INVALID_PARAMETER_DEFAULT, INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL, INVALID_TYPE_VARIABLE_CONSTRAINTS, - IncompatibleBases, NON_SUBSCRIPTABLE, POSSIBLY_UNBOUND_IMPLICIT_CALL, POSSIBLY_UNBOUND_IMPORT, + IncompatibleBases, NON_SUBSCRIPTABLE, POSSIBLY_MISSING_IMPLICIT_CALL, POSSIBLY_MISSING_IMPORT, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL, UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, report_bad_dunder_set_call, report_cannot_pop_required_field_on_typed_dict, report_implicit_return_type, @@ -60,7 +60,7 @@ use crate::types::diagnostic::{ report_invalid_attribute_assignment, report_invalid_generator_function_return_type, report_invalid_key_on_typed_dict, report_invalid_return_type, report_namedtuple_field_without_default_after_field_with_default, - report_possibly_unbound_attribute, + report_possibly_missing_attribute, }; use crate::types::diagnostic::{ INVALID_METACLASS, INVALID_OVERLOAD, INVALID_PROTOCOL, SUBCLASS_OF_FINAL_CLASS, @@ -3222,10 +3222,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { Err(err) => match err { CallDunderError::PossiblyUnbound { .. } => { if let Some(builder) = - context.report_lint(&POSSIBLY_UNBOUND_IMPLICIT_CALL, &**value) + context.report_lint(&POSSIBLY_MISSING_IMPLICIT_CALL, &**value) { builder.into_diagnostic(format_args!( - "Method `__setitem__` of type `{}` is possibly unbound", + "Method `__setitem__` of type `{}` may be missing", value_ty.display(db), )); } @@ -3306,7 +3306,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { if let Some(builder) = context.report_lint(&CALL_NON_CALLABLE, &**value) { builder.into_diagnostic(format_args!( - "Method `__setitem__` of type `{}` is possibly not \ + "Method `__setitem__` of type `{}` may not be \ callable on object of type `{}`", bindings.callable_type().display(db), value_ty.display(db), @@ -3642,7 +3642,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { }; if boundness == Boundness::PossiblyUnbound { - report_possibly_unbound_attribute( + report_possibly_missing_attribute( &self.context, target, attribute, @@ -3672,7 +3672,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } if instance_attr_boundness == Boundness::PossiblyUnbound { - report_possibly_unbound_attribute( + report_possibly_missing_attribute( &self.context, target, attribute, @@ -3752,7 +3752,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { }; if boundness == Boundness::PossiblyUnbound { - report_possibly_unbound_attribute( + report_possibly_missing_attribute( &self.context, target, attribute, @@ -3783,7 +3783,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } if class_attr_boundness == Boundness::PossiblyUnbound { - report_possibly_unbound_attribute( + report_possibly_missing_attribute( &self.context, target, attribute, @@ -4680,10 +4680,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // together if the attribute exists but is possibly-unbound. if let Some(builder) = self .context - .report_lint(&POSSIBLY_UNBOUND_IMPORT, AnyNodeRef::Alias(alias)) + .report_lint(&POSSIBLY_MISSING_IMPORT, AnyNodeRef::Alias(alias)) { builder.into_diagnostic(format_args!( - "Member `{name}` of module `{module_name}` is possibly unbound", + "Member `{name}` of module `{module_name}` may be missing", )); } } @@ -6804,7 +6804,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { Type::unknown().into() } LookupError::PossiblyUnbound(type_when_bound) => { - report_possibly_unbound_attribute( + report_possibly_missing_attribute( &self.context, attribute, &attr.id, @@ -8757,10 +8757,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } Err(err @ CallDunderError::PossiblyUnbound { .. }) => { if let Some(builder) = - context.report_lint(&POSSIBLY_UNBOUND_IMPLICIT_CALL, value_node) + context.report_lint(&POSSIBLY_MISSING_IMPLICIT_CALL, value_node) { builder.into_diagnostic(format_args!( - "Method `__getitem__` of type `{}` is possibly unbound", + "Method `__getitem__` of type `{}` may be missing", value_ty.display(db), )); } @@ -8808,7 +8808,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { CallErrorKind::PossiblyNotCallable => { if let Some(builder) = context.report_lint(&CALL_NON_CALLABLE, value_node) { builder.into_diagnostic(format_args!( - "Method `__getitem__` of type `{}` is possibly not callable on object of type `{}`", + "Method `__getitem__` of type `{}` may not be callable on object of type `{}`", bindings.callable_type().display(db), value_ty.display(db), )); @@ -8840,11 +8840,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { Place::Type(ty, boundness) => { if boundness == Boundness::PossiblyUnbound { if let Some(builder) = - context.report_lint(&POSSIBLY_UNBOUND_IMPLICIT_CALL, value_node) + context.report_lint(&POSSIBLY_MISSING_IMPLICIT_CALL, value_node) { builder.into_diagnostic(format_args!( - "Method `__class_getitem__` of type `{}` \ - is possibly unbound", + "Method `__class_getitem__` of type `{}` may be missing", value_ty.display(db), )); } diff --git a/crates/ty_server/tests/e2e/snapshots/e2e__commands__debug_command.snap b/crates/ty_server/tests/e2e/snapshots/e2e__commands__debug_command.snap index 7a59bbab2e..0f51c3aa5b 100644 --- a/crates/ty_server/tests/e2e/snapshots/e2e__commands__debug_command.snap +++ b/crates/ty_server/tests/e2e/snapshots/e2e__commands__debug_command.snap @@ -78,9 +78,9 @@ Settings: Settings { "not-iterable": Error (Default), "parameter-already-assigned": Error (Default), "positional-only-parameter-as-kwarg": Error (Default), - "possibly-unbound-attribute": Warning (Default), - "possibly-unbound-implicit-call": Warning (Default), - "possibly-unbound-import": Warning (Default), + "possibly-missing-attribute": Warning (Default), + "possibly-missing-implicit-call": Warning (Default), + "possibly-missing-import": Warning (Default), "raw-string-type-annotation": Error (Default), "redundant-cast": Warning (Default), "static-assert-error": Error (Default), diff --git a/ty.schema.json b/ty.schema.json index 29edd51b9a..f7b2b1095d 100644 --- a/ty.schema.json +++ b/ty.schema.json @@ -795,9 +795,9 @@ } ] }, - "possibly-unbound-attribute": { - "title": "detects references to possibly unbound attributes", - "description": "## What it does\nChecks for possibly unbound attributes.\n\n## Why is this bad?\nAttempting to access an unbound attribute will raise an `AttributeError` at runtime.\n\n## Examples\n```python\nclass A:\n if b:\n c = 0\n\nA.c # AttributeError: type object 'A' has no attribute 'c'\n```", + "possibly-missing-attribute": { + "title": "detects references to possibly missing attributes", + "description": "## What it does\nChecks for possibly missing attributes.\n\n## Why is this bad?\nAttempting to access a missing attribute will raise an `AttributeError` at runtime.\n\n## Examples\n```python\nclass A:\n if b:\n c = 0\n\nA.c # AttributeError: type object 'A' has no attribute 'c'\n```", "default": "warn", "oneOf": [ { @@ -805,9 +805,9 @@ } ] }, - "possibly-unbound-implicit-call": { - "title": "detects implicit calls to possibly unbound methods", - "description": "## What it does\nChecks for implicit calls to possibly unbound methods.\n\n## Why is this bad?\nExpressions such as `x[y]` and `x * y` call methods\nunder the hood (`__getitem__` and `__mul__` respectively).\nCalling an unbound method will raise an `AttributeError` at runtime.\n\n## Examples\n```python\nimport datetime\n\nclass A:\n if datetime.date.today().weekday() != 6:\n def __getitem__(self, v): ...\n\nA()[0] # TypeError: 'A' object is not subscriptable\n```", + "possibly-missing-implicit-call": { + "title": "detects implicit calls to possibly missing methods", + "description": "## What it does\nChecks for implicit calls to possibly missing methods.\n\n## Why is this bad?\nExpressions such as `x[y]` and `x * y` call methods\nunder the hood (`__getitem__` and `__mul__` respectively).\nCalling a missing method will raise an `AttributeError` at runtime.\n\n## Examples\n```python\nimport datetime\n\nclass A:\n if datetime.date.today().weekday() != 6:\n def __getitem__(self, v): ...\n\nA()[0] # TypeError: 'A' object is not subscriptable\n```", "default": "warn", "oneOf": [ { @@ -815,9 +815,9 @@ } ] }, - "possibly-unbound-import": { - "title": "detects possibly unbound imports", - "description": "## What it does\nChecks for imports of symbols that may be unbound.\n\n## Why is this bad?\nImporting an unbound module or name will raise a `ModuleNotFoundError`\nor `ImportError` at runtime.\n\n## Examples\n```python\n# module.py\nimport datetime\n\nif datetime.date.today().weekday() != 6:\n a = 1\n\n# main.py\nfrom module import a # ImportError: cannot import name 'a' from 'module'\n```", + "possibly-missing-import": { + "title": "detects possibly missing imports", + "description": "## What it does\nChecks for imports of symbols that may be missing.\n\n## Why is this bad?\nImporting a missing module or name will raise a `ModuleNotFoundError`\nor `ImportError` at runtime.\n\n## Examples\n```python\n# module.py\nimport datetime\n\nif datetime.date.today().weekday() != 6:\n a = 1\n\n# main.py\nfrom module import a # ImportError: cannot import name 'a' from 'module'\n```", "default": "warn", "oneOf": [ {