Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[red-knot] Remove Type::Unbound #13980

Merged
merged 45 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
d6cfaf9
[red-knot] remove Type::Unbound, add SymbolLookupResult
sharkdp Oct 29, 2024
b7eeae2
Fix clippy suggestions
sharkdp Oct 30, 2024
b2b802e
Fix 4 more tests
sharkdp Oct 30, 2024
1f0d0d6
Yield possibly-unbound diagnostics
sharkdp Oct 30, 2024
da91037
Fix unary test
sharkdp Oct 30, 2024
7a83b48
Fix binary/instances test
sharkdp Oct 30, 2024
5856d18
Maybe-unboundedness
sharkdp Oct 30, 2024
a0f68a4
Fix one more test
sharkdp Oct 30, 2024
adbca69
'Fix' remaining tests
sharkdp Oct 30, 2024
617673b
Get rid of clone's
sharkdp Oct 30, 2024
87755eb
Get rid of todo_unwrap_type
sharkdp Oct 30, 2024
68d46bd
Cleanup
sharkdp Oct 30, 2024
dddfaa9
Fix clippy suggestions
sharkdp Oct 30, 2024
43dfc75
Update expected diagnostics
sharkdp Oct 30, 2024
ef122b8
Rename Bound => Type
sharkdp Oct 30, 2024
75f097f
Fix member() on union types
sharkdp Oct 30, 2024
f6c1100
Get rid of most .unwrap_or(Type::Never) calls
sharkdp Oct 30, 2024
6e2b560
Remove #[ignore]
sharkdp Oct 30, 2024
81ee756
Add diagnostic for potentially unbound class member calls
sharkdp Oct 30, 2024
5a49aa2
Update diagnostics in benchmark
sharkdp Oct 30, 2024
444de93
Only match on error code
sharkdp Oct 31, 2024
750a704
Rename Boundedness => Boundness
sharkdp Oct 31, 2024
dab95ed
Import grouping
sharkdp Oct 31, 2024
d2d3157
replace_unbound_with takes a SymbolLookupResult
sharkdp Oct 31, 2024
276db1a
Update TODO comment
sharkdp Oct 31, 2024
b64d54a
bindings_ty: replace Option<Type<…>>
sharkdp Oct 31, 2024
4e10689
Remove TODO comment
sharkdp Oct 31, 2024
2167d8f
Get rid of has_definitions
sharkdp Oct 31, 2024
0df39cb
Fix semantic merge conflicts (take #4), resolve TODO for 'with'
sharkdp Oct 31, 2024
7cc5cd5
Add TODO comments
sharkdp Oct 31, 2024
eb87def
Remove unwanted diagnostic
sharkdp Oct 31, 2024
9261f2c
Remove added newline
sharkdp Oct 31, 2024
3f99b09
Fix maybe-undeclared handling
sharkdp Oct 31, 2024
0730a6c
Remove unbound=>never replacement in stdlib
sharkdp Oct 31, 2024
f0edf2b
Add & fix test case for doubly-possibly-unbound
sharkdp Oct 31, 2024
b256a1f
Rename potentially => possibly
sharkdp Oct 31, 2024
8c48000
Add as_type helper
sharkdp Oct 31, 2024
5a3bcec
Rename SymbolLookupResult => Symbol
sharkdp Oct 31, 2024
881230d
Manually fix formatting
sharkdp Oct 31, 2024
8b60e62
Remove Symbol to its own module
sharkdp Oct 31, 2024
9d83c39
Update bindings_ty documentation
sharkdp Oct 31, 2024
5341c3d
boundedness => boundness
sharkdp Oct 31, 2024
2a6917b
Rename symbol queries
sharkdp Oct 31, 2024
50d1d1c
Add TODO comment regarding declarations
sharkdp Oct 31, 2024
13bbeb2
Fix test failures
sharkdp Oct 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ b: tuple[int] = (42,)
c: tuple[str, int] = ("42", 42)
d: tuple[tuple[str, str], tuple[int, int]] = (("foo", "foo"), (42, 42))
e: tuple[str, ...] = ()
# TODO: we should not emit this error
# error: [call-possibly-unbound-method] "Method `__class_getitem__` of type `Literal[tuple]` is possibly unbound"
f: tuple[str, *tuple[int, ...], bytes] = ("42", b"42")
g: tuple[str, Unpack[tuple[int, ...]], bytes] = ("42", b"42")
h: tuple[list[int], list[int]] = ([], [])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,11 @@ class Foo:
return 42

f = Foo()

# TODO: We should emit an `unsupported-operator` error here, possibly with the information
# that `Foo.__iadd__` may be unbound as additional context.
f += "Hello, world!"

# TODO should emit a diagnostic warning that `Foo` might not have an `__iadd__` method
reveal_type(f) # revealed: int
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,20 @@
x = foo # error: [unresolved-reference] "Name `foo` used when not defined"
foo = 1

# error: [unresolved-reference]
# revealed: Unbound
# No error `unresolved-reference` diagnostic is reported for `x`. This is
# desirable because we would get a lot of cascading errors even though there
# is only one root cause (the unbound variable `foo`).

# revealed: Unknown
reveal_type(x)
```

Note: in this particular example, one could argue that the most likely error would
be a wrong order of the `x`/`foo` definitions, and so it could be desirable to infer
`Literal[1]` for the type of `x`. On the other hand, there might be a variable `fob`
a little higher up in this file, and the actual error might have been just a typo.
Inferring `Unknown` thus seems like the safest option.

## Unbound class variable

Name lookups within a class scope fall back to globals, but lookups of class attributes don't.
Expand All @@ -30,3 +39,22 @@ class C:
reveal_type(C.x) # revealed: Literal[2]
reveal_type(C.y) # revealed: Literal[1]
```

## Possibly unbound in class and global scope

```py
def bool_instance() -> bool:
return True

if bool_instance():
x = "abc"

class C:
if bool_instance():
x = 1

# error: [possibly-unresolved-reference]
y = x

reveal_type(C.y) # revealed: Literal[1] | Literal["abc"]
```
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ def bool_instance() -> bool:

if bool_instance() or (x := 1):
# error: [possibly-unresolved-reference]
reveal_type(x) # revealed: Unbound | Literal[1]
reveal_type(x) # revealed: Literal[1]

if bool_instance() and (x := 1):
# error: [possibly-unresolved-reference]
reveal_type(x) # revealed: Unbound | Literal[1]
reveal_type(x) # revealed: Literal[1]
```

## First expression is always evaluated
Expand All @@ -36,14 +36,14 @@ if (x := 1) and bool_instance():

```py
if True or (x := 1):
# TODO: infer that the second arm is never executed so type should be just "Unbound".
# TODO: infer that the second arm is never executed, and raise `unresolved-reference`.
# error: [possibly-unresolved-reference]
reveal_type(x) # revealed: Unbound | Literal[1]
reveal_type(x) # revealed: Literal[1]

if True and (x := 1):
# TODO: infer that the second arm is always executed so type should be just "Literal[1]".
# TODO: infer that the second arm is always executed, do not raise a diagnostic
# error: [possibly-unresolved-reference]
reveal_type(x) # revealed: Unbound | Literal[1]
reveal_type(x) # revealed: Literal[1]
```

## Later expressions can always use variables from earlier expressions
Expand All @@ -55,7 +55,7 @@ def bool_instance() -> bool:
bool_instance() or (x := 1) or reveal_type(x) # revealed: Literal[1]

# error: [unresolved-reference]
bool_instance() or reveal_type(y) or (y := 1) # revealed: Unbound
bool_instance() or reveal_type(y) or (y := 1) # revealed: Unknown
```

## Nested expressions
Expand All @@ -65,14 +65,14 @@ def bool_instance() -> bool:
return True

if bool_instance() or ((x := 1) and bool_instance()):
# error: "Name `x` used when possibly not defined"
reveal_type(x) # revealed: Unbound | Literal[1]
# error: [possibly-unresolved-reference]
reveal_type(x) # revealed: Literal[1]

if ((y := 1) and bool_instance()) or bool_instance():
reveal_type(y) # revealed: Literal[1]

# error: [possibly-unresolved-reference]
if (bool_instance() and (z := 1)) or reveal_type(z): # revealed: Unbound | Literal[1]
if (bool_instance() and (z := 1)) or reveal_type(z): # revealed: Literal[1]
# error: [possibly-unresolved-reference]
reveal_type(z) # revealed: Unbound | Literal[1]
reveal_type(z) # revealed: Literal[1]
```
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ x = y

reveal_type(x) # revealed: Literal[3, 4, 5]

# revealed: Unbound | Literal[2]
# revealed: Literal[2]
# error: [possibly-unresolved-reference]
reveal_type(r)

# revealed: Unbound | Literal[5]
# revealed: Literal[5]
# error: [possibly-unresolved-reference]
reveal_type(s)
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ match 0:
case 2:
y = 3

# revealed: Unbound | Literal[2, 3]
# revealed: Literal[2, 3]
# error: [possibly-unresolved-reference]
reveal_type(y)
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@ except EXCEPTIONS as f:
## Dynamic exception types

```py
def foo(x: type[AttributeError], y: tuple[type[OSError], type[RuntimeError]], z: tuple[type[BaseException], ...]):
# TODO: we should not emit these `call-possibly-unbound-method` errors for `tuple.__class_getitem__`
def foo(
x: type[AttributeError],
y: tuple[type[OSError], type[RuntimeError]], # error: [call-possibly-unbound-method]
z: tuple[type[BaseException], ...], # error: [call-possibly-unbound-method]
):
try:
help()
except x as e:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@ if flag:

x = y # error: [possibly-unresolved-reference]

# revealed: Unbound | Literal[3]
# error: [possibly-unresolved-reference]
# revealed: Literal[3]
reveal_type(x)

# revealed: Unbound | Literal[3]
# revealed: Literal[3]
# error: [possibly-unresolved-reference]
reveal_type(y)
```
Expand All @@ -40,11 +39,10 @@ if flag:
y: int = 3
x = y # error: [possibly-unresolved-reference]

# revealed: Unbound | Literal[3]
# error: [possibly-unresolved-reference]
# revealed: Literal[3]
reveal_type(x)

# revealed: Unbound | Literal[3]
# revealed: Literal[3]
# error: [possibly-unresolved-reference]
reveal_type(y)
```
Expand All @@ -58,6 +56,24 @@ reveal_type(x) # revealed: Literal[3]
reveal_type(y) # revealed: int
```

## Maybe undeclared

Importing a possibly undeclared name still gives us its declared type:

```py path=maybe_undeclared.py
def bool_instance() -> bool:
return True

if bool_instance():
x: int
```

```py
from maybe_undeclared import x

reveal_type(x) # revealed: int
```

## Reimport

```py path=c.py
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ async def foo():
async for x in Iterator():
pass

# TODO: should reveal `Unbound | Unknown` because `__aiter__` is not defined
# revealed: Unbound | @Todo
# TODO: should reveal `Unknown` because `__aiter__` is not defined
# revealed: @Todo
# error: [possibly-unresolved-reference]
reveal_type(x)
```
Expand All @@ -40,6 +40,6 @@ async def foo():
pass

# error: [possibly-unresolved-reference]
# revealed: Unbound | @Todo
# revealed: @Todo
reveal_type(x)
```
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class IntIterable:
for x in IntIterable():
pass

# revealed: Unbound | int
# revealed: int
# error: [possibly-unresolved-reference]
reveal_type(x)
```
Expand Down Expand Up @@ -87,7 +87,7 @@ class OldStyleIterable:
for x in OldStyleIterable():
pass

# revealed: Unbound | int
# revealed: int
# error: [possibly-unresolved-reference]
reveal_type(x)
```
Expand All @@ -98,7 +98,7 @@ reveal_type(x)
for x in (1, "a", b"foo"):
pass

# revealed: Unbound | Literal[1] | Literal["a"] | Literal[b"foo"]
# revealed: Literal[1] | Literal["a"] | Literal[b"foo"]
# error: [possibly-unresolved-reference]
reveal_type(x)
```
Expand All @@ -120,7 +120,7 @@ class NotIterable:
for x in NotIterable(): # error: "Object of type `NotIterable` is not iterable"
pass

# revealed: Unbound | Unknown
# revealed: Unknown
# error: [possibly-unresolved-reference]
reveal_type(x)
```
Expand Down Expand Up @@ -240,7 +240,7 @@ def coinflip() -> bool:

# TODO: we should emit a diagnostic here (it might not be iterable)
for x in Test() if coinflip() else 42:
reveal_type(x) # revealed: int | Unknown
reveal_type(x) # revealed: int
```

## Union type as iterable where one union element has invalid `__iter__` method
Expand All @@ -261,9 +261,9 @@ class Test2:
def coinflip() -> bool:
return True

# TODO: we should emit a diagnostic here (it might not be iterable)
# error: "Object of type `Test | Test2` is not iterable"
for x in Test() if coinflip() else Test2():
reveal_type(x) # revealed: int | Unknown
reveal_type(x) # revealed: Unknown
```

## Union type as iterator where one union element has no `__next__` method
Expand All @@ -277,7 +277,7 @@ class Test:
def __iter__(self) -> TestIter | int:
return TestIter()

# TODO: we should emit a diagnostic here (it might not be iterable)
# error: [not-iterable] "Object of type `Test` is not iterable"
for x in Test():
reveal_type(x) # revealed: int | Unknown
reveal_type(x) # revealed: Unknown
```
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ reveal_type(__path__) # revealed: @Todo

# TODO: this should probably be added to typeshed; not sure why it isn't?
# error: [unresolved-reference]
# revealed: Unbound
# revealed: Unknown
reveal_type(__doc__)

class X:
Expand All @@ -34,15 +34,15 @@ module globals; these are excluded:

```py path=unbound_dunders.py
# error: [unresolved-reference]
# revealed: Unbound
# revealed: Unknown
reveal_type(__getattr__)

# error: [unresolved-reference]
# revealed: Unbound
# revealed: Unknown
reveal_type(__dict__)

# error: [unresolved-reference]
# revealed: Unbound
# revealed: Unknown
reveal_type(__init__)
```

Expand All @@ -61,10 +61,10 @@ reveal_type(typing.__init__) # revealed: Literal[__init__]

# These come from `builtins.object`, not `types.ModuleType`:
# TODO: we don't currently understand `types.ModuleType` as inheriting from `object`;
# these should not reveal `Unbound`:
reveal_type(typing.__eq__) # revealed: Unbound
reveal_type(typing.__class__) # revealed: Unbound
reveal_type(typing.__module__) # revealed: Unbound
# these should not reveal `Unknown`:
reveal_type(typing.__eq__) # revealed: Unknown
reveal_type(typing.__class__) # revealed: Unknown
reveal_type(typing.__module__) # revealed: Unknown

# TODO: needs support for attribute access on instances, properties and generics;
# should be `dict[str, Any]`
Expand All @@ -78,7 +78,7 @@ where we know exactly which module we're dealing with:
```py path=__getattr__.py
import typing

reveal_type(typing.__getattr__) # revealed: Unbound
reveal_type(typing.__getattr__) # revealed: Unknown
```

## `types.ModuleType.__dict__` takes precedence over global variable `__dict__`
Expand Down Expand Up @@ -120,7 +120,7 @@ if returns_bool():
__name__ = 1

reveal_type(__file__) # revealed: Literal[42]
reveal_type(__name__) # revealed: str | Literal[1]
reveal_type(__name__) # revealed: Literal[1] | str
```

## Conditionally global or `ModuleType` attribute, with annotation
Expand All @@ -137,5 +137,5 @@ if returns_bool():
__name__: int = 1

reveal_type(__file__) # revealed: Literal[42]
reveal_type(__name__) # revealed: str | Literal[1]
reveal_type(__name__) # revealed: Literal[1] | str
```
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ if flag:
else:
class Spam: ...

# error: [call-non-callable] "Method `__class_getitem__` of type `Literal[__class_getitem__] | Unbound` is not callable on object of type `Literal[Spam, Spam]`"
# revealed: str | Unknown
# error: [call-possibly-unbound-method] "Method `__class_getitem__` of type `Literal[Spam, Spam]` is possibly unbound"
# revealed: str
reveal_type(Spam[42])
```

Expand Down
Loading
Loading