[ty] Rename "possibly unbound" diagnostics to "possibly missing" (#20492)

Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
This commit is contained in:
Renkai Ge 2025-09-23 22:26:55 +08:00 committed by GitHub
parent 4ed8c65d29
commit bf38e69870
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 213 additions and 194 deletions

View file

@ -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. Used to detect an activated Conda environment location.
If both `VIRTUAL_ENV` and `CONDA_PREFIX` are present, `VIRTUAL_ENV` will be preferred. 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 shells PATH:
one or more directory pathnames separated by os appropriate pathsep
(e.g. colons on Unix or semicolons on Windows).
### `RAYON_NUM_THREADS` ### `RAYON_NUM_THREADS`
Specifies an upper limit for the number of threads ty uses when performing work in parallel. Specifies an upper limit for the number of threads ty uses when performing work in parallel.

View file

@ -1863,21 +1863,21 @@ Use instead:
a = 20 / 0 # type: ignore a = 20 / 0 # type: ignore
``` ```
## `possibly-unbound-attribute` ## `possibly-missing-attribute`
<small> <small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · 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) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1328)
</small> </small>
**What it does** **What it does**
Checks for possibly unbound attributes. Checks for possibly missing attributes.
**Why is this bad?** **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** **Examples**
@ -1889,23 +1889,23 @@ class A:
A.c # AttributeError: type object 'A' has no attribute 'c' A.c # AttributeError: type object 'A' has no attribute 'c'
``` ```
## `possibly-unbound-implicit-call` ## `possibly-missing-implicit-call`
<small> <small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · 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) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L132)
</small> </small>
**What it does** **What it does**
Checks for implicit calls to possibly unbound methods. Checks for implicit calls to possibly missing methods.
**Why is this bad?** **Why is this bad?**
Expressions such as `x[y]` and `x * y` call methods Expressions such as `x[y]` and `x * y` call methods
under the hood (`__getitem__` and `__mul__` respectively). 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** **Examples**
@ -1919,21 +1919,21 @@ class A:
A()[0] # TypeError: 'A' object is not subscriptable A()[0] # TypeError: 'A' object is not subscriptable
``` ```
## `possibly-unbound-import` ## `possibly-missing-import`
<small> <small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · 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) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1350)
</small> </small>
**What it does** **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?** **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. or `ImportError` at runtime.
**Examples** **Examples**

View file

@ -914,7 +914,7 @@ def _(flag: bool):
reveal_type(C3.attr2) # revealed: Literal["metaclass value", "class value"] 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: diagnostic:
```py ```py
@ -924,12 +924,12 @@ def _(flag: bool):
attr1: str = "metaclass value" attr1: str = "metaclass value"
class C4(metaclass=Meta4): ... class C4(metaclass=Meta4): ...
# error: [possibly-unbound-attribute] # error: [possibly-missing-attribute]
reveal_type(C4.attr1) # revealed: str reveal_type(C4.attr1) # revealed: str
``` ```
Finally, if both the metaclass attribute and the class-level attribute are only partially defined, 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 ```py
def _(flag1: bool, flag2: bool): def _(flag1: bool, flag2: bool):
@ -941,7 +941,7 @@ def _(flag1: bool, flag2: bool):
if flag2: if flag2:
attr1 = "class value" attr1 = "class value"
# error: [possibly-unbound-attribute] # error: [possibly-missing-attribute]
reveal_type(C5.attr1) # revealed: Unknown | Literal["metaclass value", "class value"] 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 C = C1 if flag1 else C2 if flag2 else C3
# error: [possibly-unbound-attribute] "Attribute `x` on type `<class 'C1'> | <class 'C2'> | <class 'C3'>` is possibly unbound" # error: [possibly-missing-attribute] "Attribute `x` on type `<class 'C1'> | <class 'C2'> | <class 'C3'>` may be missing"
reveal_type(C.x) # revealed: Unknown | Literal[1, 3] 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 `<class 'C1'> | <class 'C2'> | <class 'C3'>`" # error: [invalid-assignment] "Object of type `Literal[100]` is not assignable to attribute `x` on type `<class 'C1'> | <class 'C2'> | <class 'C3'>`"
C.x = 100 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] 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`" # 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 C = C1 if flag1 else C2 if flag2 else C3
# error: [possibly-unbound-attribute] "Attribute `x` on type `<class 'C1'> | <class 'C2'> | <class 'C3'>` is possibly unbound" # error: [possibly-missing-attribute] "Attribute `x` on type `<class 'C1'> | <class 'C2'> | <class 'C3'>` may be missing"
reveal_type(C.x) # revealed: Unknown | Literal[1, 2, 3] reveal_type(C.x) # revealed: Unknown | Literal[1, 2, 3]
# error: [possibly-unbound-attribute] # error: [possibly-missing-attribute]
C.x = 100 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. # 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] reveal_type(C().x) # revealed: Unknown | Literal[1, 2, 3]
# error: [possibly-unbound-attribute] # error: [possibly-missing-attribute]
C().x = 100 C().x = 100
``` ```
@ -1287,16 +1287,16 @@ def _(flag: bool):
if flag: if flag:
x = 2 x = 2
# error: [possibly-unbound-attribute] # error: [possibly-missing-attribute]
reveal_type(Bar.x) # revealed: Unknown | Literal[2, 1] reveal_type(Bar.x) # revealed: Unknown | Literal[2, 1]
# error: [possibly-unbound-attribute] # error: [possibly-missing-attribute]
Bar.x = 3 Bar.x = 3
# error: [possibly-unbound-attribute] # error: [possibly-missing-attribute]
reveal_type(Bar().x) # revealed: Unknown | Literal[2, 1] reveal_type(Bar().x) # revealed: Unknown | Literal[2, 1]
# error: [possibly-unbound-attribute] # error: [possibly-missing-attribute]
Bar().x = 3 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 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 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 #### Possibly unbound and undeclared
@ -1484,17 +1484,17 @@ def _(flag: bool):
class B1: ... class B1: ...
def inner1(a_and_b: Intersection[A1, 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 reveal_type(a_and_b.x) # revealed: P
# error: [possibly-unbound-attribute] # error: [possibly-missing-attribute]
a_and_b.x = R() a_and_b.x = R()
# Same for class objects # Same for class objects
def inner1_class(a_and_b: Intersection[type[A1], type[B1]]): 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 reveal_type(a_and_b.x) # revealed: P
# error: [possibly-unbound-attribute] # error: [possibly-missing-attribute]
a_and_b.x = R() a_and_b.x = R()
class A2: class A2:
@ -1509,7 +1509,7 @@ def _(flag: bool):
# TODO: this should not be an error, we need better intersection # TODO: this should not be an error, we need better intersection
# handling in `validate_attribute_assignment` for this # handling in `validate_attribute_assignment` for this
# error: [possibly-unbound-attribute] # error: [possibly-missing-attribute]
a_and_b.x = R() a_and_b.x = R()
# Same for class objects # Same for class objects
def inner2_class(a_and_b: Intersection[type[A2], type[B1]]): def inner2_class(a_and_b: Intersection[type[A2], type[B1]]):
@ -1524,17 +1524,17 @@ def _(flag: bool):
x: Q = Q() x: Q = Q()
def inner3(a_and_b: Intersection[A3, B3]): 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 reveal_type(a_and_b.x) # revealed: P & Q
# error: [possibly-unbound-attribute] # error: [possibly-missing-attribute]
a_and_b.x = R() a_and_b.x = R()
# Same for class objects # Same for class objects
def inner3_class(a_and_b: Intersection[type[A3], type[B3]]): 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 reveal_type(a_and_b.x) # revealed: P & Q
# error: [possibly-unbound-attribute] # error: [possibly-missing-attribute]
a_and_b.x = R() a_and_b.x = R()
class A4: ... 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 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: type of the `__getattr__` method:
```py ```py

View file

@ -26,7 +26,7 @@ In particular, we should raise errors in the "possibly-undeclared-and-unbound" a
| **Diagnostic** | declared | possibly-undeclared | undeclared | | **Diagnostic** | declared | possibly-undeclared | undeclared |
| ---------------- | -------- | ------------------------- | ------------------- | | ---------------- | -------- | ------------------------- | ------------------- |
| bound | | | | | bound | | | |
| possibly-unbound | | `possibly-unbound-import` | ? | | possibly-unbound | | `possibly-missing-import` | ? |
| unbound | | ? | `unresolved-import` | | unbound | | ? | `unresolved-import` |
## Declared ## 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 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 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`: error for both `a` and `b`:
`mod.py`: `mod.py`:
@ -177,8 +177,8 @@ else:
``` ```
```py ```py
# error: [possibly-unbound-import] # error: [possibly-missing-import] "Member `a` of module `mod` may be missing"
# error: [possibly-unbound-import] # error: [possibly-missing-import] "Member `b` of module `mod` may be missing"
from mod import a, b from mod import a, b
reveal_type(a) # revealed: Literal[1] | Any reveal_type(a) # revealed: Literal[1] | Any
@ -332,8 +332,8 @@ if flag():
``` ```
```py ```py
# error: [possibly-unbound-import] # error: [possibly-missing-import]
# error: [possibly-unbound-import] # error: [possibly-missing-import]
from mod import MyInt, C from mod import MyInt, C
reveal_type(MyInt) # revealed: <class 'int'> reveal_type(MyInt) # revealed: <class 'int'>

View file

@ -19,7 +19,7 @@ b = Unit()(3.0) # error: "Object of type `Unit` is not callable"
reveal_type(b) # revealed: Unknown reveal_type(b) # revealed: Unknown
``` ```
## Possibly unbound `__call__` method ## Possibly missing `__call__` method
```py ```py
def _(flag: bool): def _(flag: bool):
@ -29,7 +29,7 @@ def _(flag: bool):
return 1 return 1
a = PossiblyNotCallable() 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 reveal_type(result) # revealed: int
``` ```
@ -105,7 +105,7 @@ reveal_type(c()) # revealed: int
## Union over callables ## Union over callables
### Possibly unbound `__call__` ### Possibly missing `__call__`
```py ```py
def outer(cond1: bool): def outer(cond1: bool):
@ -122,6 +122,6 @@ def outer(cond1: bool):
else: else:
a = Other() 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() a()
``` ```

View file

@ -158,15 +158,15 @@ def _(flag: bool) -> None:
def __new__(cls): def __new__(cls):
return object.__new__(cls) return object.__new__(cls)
# error: [possibly-unbound-implicit-call] # error: [possibly-missing-implicit-call]
reveal_type(Foo()) # revealed: Foo reveal_type(Foo()) # revealed: Foo
# error: [possibly-unbound-implicit-call] # error: [possibly-missing-implicit-call]
# error: [too-many-positional-arguments] # error: [too-many-positional-arguments]
reveal_type(Foo(1)) # revealed: Foo reveal_type(Foo(1)) # revealed: Foo
``` ```
#### Possibly unbound `__call__` on `__new__` callable #### Possibly missing `__call__` on `__new__` callable
```py ```py
def _(flag: bool) -> None: def _(flag: bool) -> None:
@ -178,11 +178,11 @@ def _(flag: bool) -> None:
class Foo: class Foo:
__new__ = Callable() __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 reveal_type(Foo(1)) # revealed: Foo
# TODO should be - error: [missing-argument] "No argument provided for required parameter `x` of bound method `__call__`" # 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 # 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 reveal_type(Foo()) # revealed: Foo
``` ```
@ -294,11 +294,11 @@ def _(flag: bool) -> None:
class Foo: class Foo:
__init__ = Callable() __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 reveal_type(Foo(1)) # revealed: Foo
# TODO should be - error: [missing-argument] "No argument provided for required parameter `x` of bound method `__call__`" # 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 # 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 reveal_type(Foo()) # revealed: Foo
``` ```

View file

@ -114,7 +114,11 @@ def _(flag: bool):
this_fails = ThisFails() 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 reveal_type(this_fails[0]) # revealed: Unknown | str
``` ```
@ -270,6 +274,11 @@ def _(flag: bool):
return str(key) return str(key)
c = C() 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 reveal_type(c[0]) # revealed: str
``` ```

View file

@ -325,7 +325,7 @@ class D(metaclass=Meta):
reveal_type(D.f(1)) # revealed: Literal["a"] 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 ```py
def flag() -> bool: def flag() -> bool:

View file

@ -219,7 +219,7 @@ def f(x: C | D):
s = super(A, x) s = super(A, x)
reveal_type(s) # revealed: <super: <class 'A'>, C> | <super: <class 'A'>, D> reveal_type(s) # revealed: <super: <class 'A'>, C> | <super: <class 'A'>, D>
# error: [possibly-unbound-attribute] "Attribute `b` on type `<super: <class 'A'>, C> | <super: <class 'A'>, D>` is possibly unbound" # error: [possibly-missing-attribute] "Attribute `b` on type `<super: <class 'A'>, C> | <super: <class 'A'>, D>` may be missing"
s.b s.b
def f(flag: bool): def f(flag: bool):
@ -259,7 +259,7 @@ def f(flag: bool):
reveal_type(s.x) # revealed: Unknown | Literal[1, 2] reveal_type(s.x) # revealed: Unknown | Literal[1, 2]
reveal_type(s.y) # revealed: int | str reveal_type(s.y) # revealed: int | str
# error: [possibly-unbound-attribute] "Attribute `a` on type `<super: <class 'B'>, B> | <super: <class 'D'>, D>` is possibly unbound" # error: [possibly-missing-attribute] "Attribute `a` on type `<super: <class 'B'>, B> | <super: <class 'D'>, D>` may be missing"
reveal_type(s.a) # revealed: str reveal_type(s.a) # revealed: str
``` ```

View file

@ -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"] 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: method with an underlying class level attribute, if present:
```py ```py
@ -365,7 +365,7 @@ def _(flag: bool):
meta_data_descriptor1: Literal["value on class"] = "value on class" meta_data_descriptor1: Literal["value on class"] = "value on class"
reveal_type(C5.meta_data_descriptor1) # revealed: Literal["data", "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"] 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 # 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"]`" # 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 C5.meta_data_descriptor1 = None
# error: [possibly-unbound-attribute] # error: [possibly-missing-attribute]
C5.meta_data_descriptor2 = 1 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): metaclass attribute (unless it's a data descriptor, which always takes precedence):
```py ```py
@ -401,7 +401,7 @@ def _(flag: bool):
reveal_type(C6.attribute1) # revealed: Literal["data"] reveal_type(C6.attribute1) # revealed: Literal["data"]
reveal_type(C6.attribute2) # revealed: Literal["non-data", "value on class"] reveal_type(C6.attribute2) # revealed: Literal["non-data", "value on class"]
reveal_type(C6.attribute3) # revealed: Literal["value on metaclass", "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"] reveal_type(C6.attribute4) # revealed: Literal["value on class"]
``` ```
@ -756,16 +756,16 @@ def _(flag: bool):
non_data: NonDataDescriptor = NonDataDescriptor() non_data: NonDataDescriptor = NonDataDescriptor()
data: DataDescriptor = DataDescriptor() data: DataDescriptor = DataDescriptor()
# error: [possibly-unbound-attribute] "Attribute `non_data` on type `<class 'PossiblyUnbound'>` is possibly unbound" # error: [possibly-missing-attribute] "Attribute `non_data` on type `<class 'PossiblyUnbound'>` may be missing"
reveal_type(PossiblyUnbound.non_data) # revealed: int 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 reveal_type(PossiblyUnbound().non_data) # revealed: int
# error: [possibly-unbound-attribute] "Attribute `data` on type `<class 'PossiblyUnbound'>` is possibly unbound" # error: [possibly-missing-attribute] "Attribute `data` on type `<class 'PossiblyUnbound'>` may be missing"
reveal_type(PossiblyUnbound.data) # revealed: int 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 reveal_type(PossiblyUnbound().data) # revealed: int
``` ```

View file

@ -69,7 +69,7 @@ instance = C()
instance.non_existent = 1 # error: [unresolved-attribute] 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: 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: if flag:
attr: int = 0 attr: int = 0
C.attr = 1 # error: [possibly-unbound-attribute] C.attr = 1 # error: [possibly-missing-attribute]
instance = C() instance = C()
instance.attr = 1 # error: [possibly-unbound-attribute] instance.attr = 1 # error: [possibly-missing-attribute]
``` ```
## Data descriptors ## Data descriptors

View file

@ -23,7 +23,7 @@ async def main() -> None:
await MissingAwait() # error: [invalid-await] 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. This diagnostic also points to the method definition if available.

View file

@ -116,7 +116,7 @@ def _(n: int):
# error: [invalid-argument-type] "Argument to function `f5` is incorrect: Expected `str`, found `Literal[3]`" # 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: [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 `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) x = f(3)
``` ```

View file

@ -26,7 +26,7 @@ def _(flag: bool):
reveal_type(A.union_declared) # revealed: int | str reveal_type(A.union_declared) # revealed: int | str
# error: [possibly-unbound-attribute] "Attribute `possibly_unbound` on type `<class 'A'>` is possibly unbound" # error: [possibly-missing-attribute] "Attribute `possibly_unbound` on type `<class 'A'>` may be missing"
reveal_type(A.possibly_unbound) # revealed: str reveal_type(A.possibly_unbound) # revealed: str
# error: [unresolved-attribute] "Type `<class 'A'>` has no attribute `non_existent`" # error: [unresolved-attribute] "Type `<class 'A'>` has no attribute `non_existent`"

View file

@ -22,7 +22,7 @@ reveal_type(y)
``` ```
```py ```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 from maybe_unbound import x, y
reveal_type(x) # revealed: Unknown | Literal[3] 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: Importing an annotated name prefers the declared type over the inferred type:
```py ```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 from maybe_unbound_annotated import x, y
reveal_type(x) # revealed: Unknown | Literal[3] reveal_type(x) # revealed: Unknown | Literal[3]

View file

@ -306,7 +306,7 @@ The following scenarios are when a re-export happens conditionally in a stub fil
### Global import ### Global import
```py ```py
# error: "Member `Foo` of module `a` is possibly unbound" # error: "Member `Foo` of module `a` may be missing"
from a import Foo from a import Foo
reveal_type(Foo) # revealed: str 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. the other does not.
```py ```py
# error: "Member `Foo` of module `a` is possibly unbound" # error: "Member `Foo` of module `a` may be missing"
from a import Foo from a import Foo
reveal_type(Foo) # revealed: <class 'Foo'> reveal_type(Foo) # revealed: <class 'Foo'>
@ -365,7 +365,7 @@ class Foo: ...
### Re-export in one branch ### Re-export in one branch
```py ```py
# error: "Member `Foo` of module `a` is possibly unbound" # error: "Member `Foo` of module `a` may be missing"
from a import Foo from a import Foo
reveal_type(Foo) # revealed: <class 'Foo'> reveal_type(Foo) # revealed: <class 'Foo'>

View file

@ -88,7 +88,7 @@ async def foo():
reveal_type(x) # revealed: Unknown reveal_type(x) # revealed: Unknown
``` ```
### Possibly unbound `__anext__` method ### Possibly missing `__anext__` method
```py ```py
from typing_extensions import reveal_type from typing_extensions import reveal_type
@ -108,7 +108,7 @@ async def foo(flag: bool):
reveal_type(x) # revealed: int reveal_type(x) # revealed: int
``` ```
### Possibly unbound `__aiter__` method ### Possibly missing `__aiter__` method
```py ```py
from typing_extensions import reveal_type from typing_extensions import reveal_type

View file

@ -363,7 +363,7 @@ for x in Bad():
reveal_type(x) # revealed: Unknown 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 ```py
def _(flag: bool): def _(flag: bool):
@ -412,7 +412,7 @@ for y in Iterable2():
reveal_type(y) # revealed: Unknown reveal_type(y) # revealed: Unknown
``` ```
## Possibly unbound `__iter__` and bad `__getitem__` method ## Possibly missing `__iter__` and bad `__getitem__` method
<!-- snapshot-diagnostics --> <!-- snapshot-diagnostics -->
@ -438,12 +438,12 @@ def _(flag: bool):
reveal_type(x) # revealed: int | bytes 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: 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 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 - `__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 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 reveal_type(x) # revealed: int
``` ```
## Possibly unbound `__iter__` and possibly unbound `__getitem__` ## Possibly missing `__iter__` and possibly missing `__getitem__`
<!-- snapshot-diagnostics --> <!-- snapshot-diagnostics -->
@ -560,7 +560,7 @@ for x in Iterable():
reveal_type(x) # revealed: int 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 Here, we should not emit a diagnostic: if `__iter__` is unbound, we should fallback to
`__getitem__`: `__getitem__`:
@ -694,7 +694,7 @@ def _(flag: bool):
reveal_type(y) # revealed: str | int reveal_type(y) # revealed: str | int
``` ```
## Possibly unbound `__iter__` and possibly invalid `__getitem__` ## Possibly missing `__iter__` and possibly invalid `__getitem__`
<!-- snapshot-diagnostics --> <!-- snapshot-diagnostics -->

View file

@ -135,9 +135,9 @@ a.b = B()
reveal_type(a.b) # revealed: B reveal_type(a.b) # revealed: B
reveal_type(a.b.c1) # revealed: C | None reveal_type(a.b.c1) # revealed: C | None
reveal_type(a.b.c2) # 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 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 reveal_type(a.b.c2.d) # revealed: D | None
``` ```
@ -295,9 +295,9 @@ class C:
reveal_type(b.a.x[0]) # revealed: Literal[0] reveal_type(b.a.x[0]) # revealed: Literal[0]
def _(): def _():
# error: [possibly-unbound-attribute] # error: [possibly-missing-attribute]
reveal_type(b.a.x[0]) # revealed: Unknown | int | None 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.x) # revealed: Unknown | list[int | None]
reveal_type(b.a) # revealed: Unknown | A | None reveal_type(b.a) # revealed: Unknown | A | None
``` ```

View file

@ -161,7 +161,7 @@ class _:
a.b = B() a.b = B()
class _: class _:
# error: [possibly-unbound-attribute] # error: [possibly-missing-attribute]
reveal_type(a.b.c1.d) # revealed: D | None reveal_type(a.b.c1.d) # revealed: D | None
reveal_type(a.b.c1) # revealed: C | None reveal_type(a.b.c1) # revealed: C | None
``` ```

View file

@ -60,7 +60,7 @@ def _(obj: WithSpam):
``` ```
When a class may or may not have a `spam` attribute, `hasattr` narrowing can provide evidence that 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 ```py
def returns_bool() -> bool: def returns_bool() -> bool:
@ -71,7 +71,7 @@ class MaybeWithSpam:
spam: int = 42 spam: int = 42
def _(obj: MaybeWithSpam): def _(obj: MaybeWithSpam):
# error: [possibly-unbound-attribute] # error: [possibly-missing-attribute]
reveal_type(obj.spam) # revealed: int reveal_type(obj.spam) # revealed: int
if hasattr(obj, "spam"): if hasattr(obj, "spam"):
@ -81,7 +81,7 @@ def _(obj: MaybeWithSpam):
reveal_type(obj) # revealed: MaybeWithSpam & ~<Protocol with members 'spam'> reveal_type(obj) # revealed: MaybeWithSpam & ~<Protocol with members 'spam'>
# TODO: Ideally, we would emit `[unresolved-attribute]` and reveal `Unknown` here: # 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 reveal_type(obj.spam) # revealed: int
``` ```

View file

@ -16,7 +16,7 @@ class C:
if flag: if flag:
x = 2 x = 2
# error: [possibly-unbound-attribute] "Attribute `x` on type `<class 'C'>` is possibly unbound" # error: [possibly-missing-attribute] "Attribute `x` on type `<class 'C'>` may be missing"
reveal_type(C.x) # revealed: Unknown | Literal[2] reveal_type(C.x) # revealed: Unknown | Literal[2]
reveal_type(C.y) # revealed: Unknown | Literal[1] reveal_type(C.y) # revealed: Unknown | Literal[1]
``` ```
@ -52,7 +52,7 @@ class C:
elif coinflip(): elif coinflip():
x: str = "abc" x: str = "abc"
# error: [possibly-unbound-attribute] # error: [possibly-missing-attribute]
reveal_type(C.x) # revealed: int | str reveal_type(C.x) # revealed: int | str
``` ```

View file

@ -3,7 +3,7 @@ source: crates/ty_test/src/lib.rs
expression: snapshot 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 mdtest path: crates/ty_python_semantic/resources/mdtest/loops/async_for.md
--- ---

View file

@ -3,7 +3,7 @@ source: crates/ty_test/src/lib.rs
expression: snapshot 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 mdtest path: crates/ty_python_semantic/resources/mdtest/loops/async_for.md
--- ---

View file

@ -3,7 +3,7 @@ source: crates/ty_test/src/lib.rs
expression: snapshot 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 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: 3 | if flag:
4 | attr: int = 0 4 | attr: int = 0
5 | 5 |
6 | C.attr = 1 # error: [possibly-unbound-attribute] 6 | C.attr = 1 # error: [possibly-missing-attribute]
7 | 7 |
8 | instance = C() 8 | instance = C()
9 | instance.attr = 1 # error: [possibly-unbound-attribute] 9 | instance.attr = 1 # error: [possibly-missing-attribute]
``` ```
# Diagnostics # Diagnostics
``` ```
warning[possibly-unbound-attribute]: Attribute `attr` on type `<class 'C'>` is possibly unbound warning[possibly-missing-attribute]: Attribute `attr` on type `<class 'C'>` may be missing
--> src/mdtest_snippet.py:6:5 --> src/mdtest_snippet.py:6:5
| |
4 | attr: int = 0 4 | attr: int = 0
5 | 5 |
6 | C.attr = 1 # error: [possibly-unbound-attribute] 6 | C.attr = 1 # error: [possibly-missing-attribute]
| ^^^^^^ | ^^^^^^
7 | 7 |
8 | instance = C() 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 --> src/mdtest_snippet.py:9:5
| |
8 | instance = C() 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
``` ```

View file

@ -3,7 +3,7 @@ source: crates/ty_test/src/lib.rs
expression: snapshot 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 mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
--- ---

View file

@ -3,7 +3,7 @@ source: crates/ty_test/src/lib.rs
expression: snapshot 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 mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
--- ---

View file

@ -3,7 +3,7 @@ source: crates/ty_test/src/lib.rs
expression: snapshot 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 mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
--- ---

View file

@ -3,7 +3,7 @@ source: crates/ty_test/src/lib.rs
expression: snapshot 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 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 | --------------- method defined here
6 | yield 6 | yield
| |
info: `__await__` is possibly unbound info: `__await__` may be missing
info: rule `invalid-await` is enabled by default info: rule `invalid-await` is enabled by default
``` ```

View file

@ -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]`" 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" 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" 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) 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 --> src/mdtest_snippet.py:60:9
| |
58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable" 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) 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 --> src/mdtest_snippet.py:60:9
| |
58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable" 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) 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 --> src/mdtest_snippet.py:60:9
| |
58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable" 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) 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 --> src/mdtest_snippet.py:60:9
| |
58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable" 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) 60 | x = f(3)
| ^^^^ | ^^^^
| |
@ -162,7 +162,7 @@ error[invalid-argument-type]: Argument to function `f2` is incorrect
--> src/mdtest_snippet.py:60:11 --> src/mdtest_snippet.py:60:11
| |
58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable" 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) 60 | x = f(3)
| ^ Expected `str`, found `Literal[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 --> src/mdtest_snippet.py:60:11
| |
58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable" 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) 60 | x = f(3)
| ^ Argument type `Literal[3]` does not satisfy upper bound `str` of type variable `T` | ^ 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 --> src/mdtest_snippet.py:60:11
| |
58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable" 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) 60 | x = f(3)
| ^ Expected `str`, found `Literal[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 --> src/mdtest_snippet.py:60:11
| |
58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable" 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) 60 | x = f(3)
| ^ | ^
| |

View file

@ -1530,7 +1530,7 @@ if flag():
``` ```
```py ```py
# error: [possibly-unbound-import] # error: [possibly-missing-import]
from module import symbol from module import symbol
``` ```

View file

@ -14,7 +14,11 @@ a = NotSubscriptable()[0] # error: "Cannot subscript object of type `NotSubscri
class NotSubscriptable: class NotSubscriptable:
__getitem__ = None __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] a = NotSubscriptable()[0]
``` ```
@ -82,7 +86,7 @@ class NoSetitem:
__setitem__ = None __setitem__ = None
a = NoSetitem() 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 ## Valid `__setitem__` method

View file

@ -198,7 +198,7 @@ import sys
if sys.platform == "win32": if sys.platform == "win32":
# TODO: we should not emit an error here # TODO: we should not emit an error here
# error: [possibly-unbound-attribute] # error: [possibly-missing-attribute]
sys.getwindowsversion() sys.getwindowsversion()
``` ```

View file

@ -113,7 +113,7 @@ async def _(flag: bool):
class NotAContextManager: ... class NotAContextManager: ...
context_expr = Manager1() if flag else 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: async with context_expr as f:
reveal_type(f) # revealed: str reveal_type(f) # revealed: str
``` ```
@ -129,7 +129,7 @@ async def _(flag: bool):
async def __exit__(self, *args): ... 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: async with Manager() as f:
reveal_type(f) # revealed: CoroutineType[Any, Any, str] reveal_type(f) # revealed: CoroutineType[Any, Any, str]
``` ```

View file

@ -113,7 +113,7 @@ def _(flag: bool):
class NotAContextManager: ... class NotAContextManager: ...
context_expr = Manager1() if flag else 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: with context_expr as f:
reveal_type(f) # revealed: str reveal_type(f) # revealed: str
``` ```
@ -129,7 +129,7 @@ def _(flag: bool):
def __exit__(self, *args): ... 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: with Manager() as f:
reveal_type(f) # revealed: str reveal_type(f) # revealed: str
``` ```

View file

@ -8,7 +8,7 @@ use bitflags::bitflags;
use call::{CallDunderError, CallError, CallErrorKind}; use call::{CallDunderError, CallError, CallErrorKind};
use context::InferContext; use context::InferContext;
use diagnostic::{ 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, UNAVAILABLE_IMPLICIT_SUPER_ARGUMENTS,
}; };
use ruff_db::diagnostic::{Annotation, Diagnostic, Span, SubDiagnostic, SubDiagnosticSeverity}; 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) Ok(Truthiness::Ambiguous)
} }
@ -8148,7 +8148,7 @@ impl<'db> AwaitError<'db> {
} }
} }
Self::Call(CallDunderError::PossiblyUnbound(bindings)) => { 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) { if let Some(definition_spans) = bindings.callable_type().function_spans(db) {
diag.annotate( diag.annotate(
Annotation::secondary(definition_spans.signature) Annotation::secondary(definition_spans.signature)
@ -8255,7 +8255,7 @@ impl<'db> ContextManagerError<'db> {
match call_dunder_error { match call_dunder_error {
CallDunderError::MethodNotAvailable => format!("it does not implement `{name}`"), CallDunderError::MethodNotAvailable => format!("it does not implement `{name}`"),
CallDunderError::PossiblyUnbound(_) => { 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. // TODO: Use more specific error messages for the different error cases.
// E.g. hint toward the union variant that doesn't correctly implement enter, // E.g. hint toward the union variant that doesn't correctly implement enter,
@ -8272,7 +8272,7 @@ impl<'db> ContextManagerError<'db> {
name_b: &str| { name_b: &str| {
match (error_a, error_b) { match (error_a, error_b) {
(CallDunderError::PossiblyUnbound(_), CallDunderError::PossiblyUnbound(_)) => { (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) => { (CallDunderError::MethodNotAvailable, CallDunderError::MethodNotAvailable) => {
format!("it does not implement `{name_a}` and `{name_b}`") 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. /// 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 /// 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> }, 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 { let report_init_error = |call_dunder_error: &CallDunderError<'db>| match call_dunder_error {
CallDunderError::MethodNotAvailable => { CallDunderError::MethodNotAvailable => {
if let Some(builder) = 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 // 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. // 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) => { CallDunderError::PossiblyUnbound(bindings) => {
if let Some(builder) = 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!( 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()), context_expression_type.display(context.db()),
)); ));
} }
@ -9026,10 +9026,10 @@ impl<'db> ConstructorCallError<'db> {
let report_new_error = |error: &DunderNewCallError<'db>| match error { let report_new_error = |error: &DunderNewCallError<'db>| match error {
DunderNewCallError::PossiblyUnbound(call_error) => { DunderNewCallError::PossiblyUnbound(call_error) => {
if let Some(builder) = 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!( 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()), context_expression_type.display(context.db()),
)); ));
} }

View file

@ -1758,7 +1758,7 @@ impl<'db> CallableBinding<'db> {
if self.dunder_call_is_possibly_unbound { if self.dunder_call_is_possibly_unbound {
if let Some(builder) = context.report_lint(&CALL_NON_CALLABLE, node) { if let Some(builder) = context.report_lint(&CALL_NON_CALLABLE, node) {
let mut diag = builder.into_diagnostic(format_args!( 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()), self.callable_type.display(context.db()),
)); ));
if let Some(union_diag) = union_diag { if let Some(union_diag) = union_diag {

View file

@ -38,7 +38,7 @@ use std::fmt::Formatter;
pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) { pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
registry.register_lint(&AMBIGUOUS_PROTOCOL_MEMBER); registry.register_lint(&AMBIGUOUS_PROTOCOL_MEMBER);
registry.register_lint(&CALL_NON_CALLABLE); 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_ARGUMENT_FORMS);
registry.register_lint(&CONFLICTING_DECLARATIONS); registry.register_lint(&CONFLICTING_DECLARATIONS);
registry.register_lint(&CONFLICTING_METACLASS); 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(&NOT_ITERABLE);
registry.register_lint(&UNSUPPORTED_BOOL_CONVERSION); registry.register_lint(&UNSUPPORTED_BOOL_CONVERSION);
registry.register_lint(&PARAMETER_ALREADY_ASSIGNED); registry.register_lint(&PARAMETER_ALREADY_ASSIGNED);
registry.register_lint(&POSSIBLY_UNBOUND_ATTRIBUTE); registry.register_lint(&POSSIBLY_MISSING_ATTRIBUTE);
registry.register_lint(&POSSIBLY_UNBOUND_IMPORT); registry.register_lint(&POSSIBLY_MISSING_IMPORT);
registry.register_lint(&POSSIBLY_UNRESOLVED_REFERENCE); registry.register_lint(&POSSIBLY_UNRESOLVED_REFERENCE);
registry.register_lint(&SUBCLASS_OF_FINAL_CLASS); registry.register_lint(&SUBCLASS_OF_FINAL_CLASS);
registry.register_lint(&TYPE_ASSERTION_FAILURE); registry.register_lint(&TYPE_ASSERTION_FAILURE);
@ -131,12 +131,12 @@ declare_lint! {
declare_lint! { declare_lint! {
/// ## What it does /// ## What it does
/// Checks for implicit calls to possibly unbound methods. /// Checks for implicit calls to possibly missing methods.
/// ///
/// ## Why is this bad? /// ## Why is this bad?
/// Expressions such as `x[y]` and `x * y` call methods /// Expressions such as `x[y]` and `x * y` call methods
/// under the hood (`__getitem__` and `__mul__` respectively). /// 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 /// ## Examples
/// ```python /// ```python
@ -148,8 +148,8 @@ declare_lint! {
/// ///
/// A()[0] # TypeError: 'A' object is not subscriptable /// A()[0] # TypeError: 'A' object is not subscriptable
/// ``` /// ```
pub(crate) static POSSIBLY_UNBOUND_IMPLICIT_CALL = { pub(crate) static POSSIBLY_MISSING_IMPLICIT_CALL = {
summary: "detects implicit calls to possibly unbound methods", summary: "detects implicit calls to possibly missing methods",
status: LintStatus::preview("1.0.0"), status: LintStatus::preview("1.0.0"),
default_level: Level::Warn, default_level: Level::Warn,
} }
@ -1327,10 +1327,10 @@ declare_lint! {
declare_lint! { declare_lint! {
/// ## What it does /// ## What it does
/// Checks for possibly unbound attributes. /// Checks for possibly missing attributes.
/// ///
/// ## Why is this bad? /// ## 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 /// ## Examples
/// ```python /// ```python
@ -1340,8 +1340,8 @@ declare_lint! {
/// ///
/// A.c # AttributeError: type object 'A' has no attribute 'c' /// A.c # AttributeError: type object 'A' has no attribute 'c'
/// ``` /// ```
pub(crate) static POSSIBLY_UNBOUND_ATTRIBUTE = { pub(crate) static POSSIBLY_MISSING_ATTRIBUTE = {
summary: "detects references to possibly unbound attributes", summary: "detects references to possibly missing attributes",
status: LintStatus::preview("1.0.0"), status: LintStatus::preview("1.0.0"),
default_level: Level::Warn, default_level: Level::Warn,
} }
@ -1349,10 +1349,10 @@ declare_lint! {
declare_lint! { declare_lint! {
/// ## What it does /// ## 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? /// ## 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. /// or `ImportError` at runtime.
/// ///
/// ## Examples /// ## Examples
@ -1366,8 +1366,8 @@ declare_lint! {
/// # main.py /// # main.py
/// from module import a # ImportError: cannot import name 'a' from 'module' /// from module import a # ImportError: cannot import name 'a' from 'module'
/// ``` /// ```
pub(crate) static POSSIBLY_UNBOUND_IMPORT = { pub(crate) static POSSIBLY_MISSING_IMPORT = {
summary: "detects possibly unbound imports", summary: "detects possibly missing imports",
status: LintStatus::preview("1.0.0"), status: LintStatus::preview("1.0.0"),
default_level: Level::Warn, 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")); 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, context: &InferContext,
target: &ast::ExprAttribute, target: &ast::ExprAttribute,
attribute: &str, attribute: &str,
object_ty: Type, 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; return;
}; };
builder.into_diagnostic(format_args!( builder.into_diagnostic(format_args!(
"Attribute `{attribute}` on type `{}` is possibly unbound", "Attribute `{attribute}` on type `{}` may be missing",
object_ty.display(context.db()), object_ty.display(context.db()),
)); ));
} }
@ -2793,7 +2793,7 @@ pub(crate) fn report_invalid_or_unsupported_base(
CallDunderError::PossiblyUnbound(_) => { CallDunderError::PossiblyUnbound(_) => {
explain_mro_entries(&mut diagnostic); explain_mro_entries(&mut diagnostic);
diagnostic.info(format_args!( 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) base_type.display(db)
)); ));
} }

View file

@ -52,7 +52,7 @@ use crate::types::diagnostic::{
INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, INVALID_DECLARATION, INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, INVALID_DECLARATION,
INVALID_GENERIC_CLASS, INVALID_KEY, INVALID_NAMED_TUPLE, INVALID_PARAMETER_DEFAULT, INVALID_GENERIC_CLASS, INVALID_KEY, INVALID_NAMED_TUPLE, INVALID_PARAMETER_DEFAULT,
INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL, INVALID_TYPE_VARIABLE_CONSTRAINTS, 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, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL, UNRESOLVED_IMPORT,
UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, report_bad_dunder_set_call, UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, report_bad_dunder_set_call,
report_cannot_pop_required_field_on_typed_dict, report_implicit_return_type, 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_attribute_assignment, report_invalid_generator_function_return_type,
report_invalid_key_on_typed_dict, report_invalid_return_type, report_invalid_key_on_typed_dict, report_invalid_return_type,
report_namedtuple_field_without_default_after_field_with_default, report_namedtuple_field_without_default_after_field_with_default,
report_possibly_unbound_attribute, report_possibly_missing_attribute,
}; };
use crate::types::diagnostic::{ use crate::types::diagnostic::{
INVALID_METACLASS, INVALID_OVERLOAD, INVALID_PROTOCOL, SUBCLASS_OF_FINAL_CLASS, INVALID_METACLASS, INVALID_OVERLOAD, INVALID_PROTOCOL, SUBCLASS_OF_FINAL_CLASS,
@ -3222,10 +3222,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
Err(err) => match err { Err(err) => match err {
CallDunderError::PossiblyUnbound { .. } => { CallDunderError::PossiblyUnbound { .. } => {
if let Some(builder) = 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!( builder.into_diagnostic(format_args!(
"Method `__setitem__` of type `{}` is possibly unbound", "Method `__setitem__` of type `{}` may be missing",
value_ty.display(db), 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) if let Some(builder) = context.report_lint(&CALL_NON_CALLABLE, &**value)
{ {
builder.into_diagnostic(format_args!( builder.into_diagnostic(format_args!(
"Method `__setitem__` of type `{}` is possibly not \ "Method `__setitem__` of type `{}` may not be \
callable on object of type `{}`", callable on object of type `{}`",
bindings.callable_type().display(db), bindings.callable_type().display(db),
value_ty.display(db), value_ty.display(db),
@ -3642,7 +3642,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}; };
if boundness == Boundness::PossiblyUnbound { if boundness == Boundness::PossiblyUnbound {
report_possibly_unbound_attribute( report_possibly_missing_attribute(
&self.context, &self.context,
target, target,
attribute, attribute,
@ -3672,7 +3672,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
} }
if instance_attr_boundness == Boundness::PossiblyUnbound { if instance_attr_boundness == Boundness::PossiblyUnbound {
report_possibly_unbound_attribute( report_possibly_missing_attribute(
&self.context, &self.context,
target, target,
attribute, attribute,
@ -3752,7 +3752,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}; };
if boundness == Boundness::PossiblyUnbound { if boundness == Boundness::PossiblyUnbound {
report_possibly_unbound_attribute( report_possibly_missing_attribute(
&self.context, &self.context,
target, target,
attribute, attribute,
@ -3783,7 +3783,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
} }
if class_attr_boundness == Boundness::PossiblyUnbound { if class_attr_boundness == Boundness::PossiblyUnbound {
report_possibly_unbound_attribute( report_possibly_missing_attribute(
&self.context, &self.context,
target, target,
attribute, attribute,
@ -4680,10 +4680,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// together if the attribute exists but is possibly-unbound. // together if the attribute exists but is possibly-unbound.
if let Some(builder) = self if let Some(builder) = self
.context .context
.report_lint(&POSSIBLY_UNBOUND_IMPORT, AnyNodeRef::Alias(alias)) .report_lint(&POSSIBLY_MISSING_IMPORT, AnyNodeRef::Alias(alias))
{ {
builder.into_diagnostic(format_args!( 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() Type::unknown().into()
} }
LookupError::PossiblyUnbound(type_when_bound) => { LookupError::PossiblyUnbound(type_when_bound) => {
report_possibly_unbound_attribute( report_possibly_missing_attribute(
&self.context, &self.context,
attribute, attribute,
&attr.id, &attr.id,
@ -8757,10 +8757,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
} }
Err(err @ CallDunderError::PossiblyUnbound { .. }) => { Err(err @ CallDunderError::PossiblyUnbound { .. }) => {
if let Some(builder) = 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!( builder.into_diagnostic(format_args!(
"Method `__getitem__` of type `{}` is possibly unbound", "Method `__getitem__` of type `{}` may be missing",
value_ty.display(db), value_ty.display(db),
)); ));
} }
@ -8808,7 +8808,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
CallErrorKind::PossiblyNotCallable => { CallErrorKind::PossiblyNotCallable => {
if let Some(builder) = context.report_lint(&CALL_NON_CALLABLE, value_node) { if let Some(builder) = context.report_lint(&CALL_NON_CALLABLE, value_node) {
builder.into_diagnostic(format_args!( 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), bindings.callable_type().display(db),
value_ty.display(db), value_ty.display(db),
)); ));
@ -8840,11 +8840,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
Place::Type(ty, boundness) => { Place::Type(ty, boundness) => {
if boundness == Boundness::PossiblyUnbound { if boundness == Boundness::PossiblyUnbound {
if let Some(builder) = 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!( builder.into_diagnostic(format_args!(
"Method `__class_getitem__` of type `{}` \ "Method `__class_getitem__` of type `{}` may be missing",
is possibly unbound",
value_ty.display(db), value_ty.display(db),
)); ));
} }

View file

@ -78,9 +78,9 @@ Settings: Settings {
"not-iterable": Error (Default), "not-iterable": Error (Default),
"parameter-already-assigned": Error (Default), "parameter-already-assigned": Error (Default),
"positional-only-parameter-as-kwarg": Error (Default), "positional-only-parameter-as-kwarg": Error (Default),
"possibly-unbound-attribute": Warning (Default), "possibly-missing-attribute": Warning (Default),
"possibly-unbound-implicit-call": Warning (Default), "possibly-missing-implicit-call": Warning (Default),
"possibly-unbound-import": Warning (Default), "possibly-missing-import": Warning (Default),
"raw-string-type-annotation": Error (Default), "raw-string-type-annotation": Error (Default),
"redundant-cast": Warning (Default), "redundant-cast": Warning (Default),
"static-assert-error": Error (Default), "static-assert-error": Error (Default),

18
ty.schema.json generated
View file

@ -795,9 +795,9 @@
} }
] ]
}, },
"possibly-unbound-attribute": { "possibly-missing-attribute": {
"title": "detects references to possibly unbound attributes", "title": "detects references to possibly missing 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```", "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", "default": "warn",
"oneOf": [ "oneOf": [
{ {
@ -805,9 +805,9 @@
} }
] ]
}, },
"possibly-unbound-implicit-call": { "possibly-missing-implicit-call": {
"title": "detects implicit calls to possibly unbound methods", "title": "detects implicit calls to possibly missing 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```", "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", "default": "warn",
"oneOf": [ "oneOf": [
{ {
@ -815,9 +815,9 @@
} }
] ]
}, },
"possibly-unbound-import": { "possibly-missing-import": {
"title": "detects possibly unbound imports", "title": "detects possibly missing 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```", "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", "default": "warn",
"oneOf": [ "oneOf": [
{ {