Skip to content

Commit

Permalink
[red-knot] Use Unknown | T_inferred for undeclared public symbols
Browse files Browse the repository at this point in the history
  • Loading branch information
sharkdp committed Jan 22, 2025
1 parent 13e7afc commit fd883fb
Show file tree
Hide file tree
Showing 30 changed files with 330 additions and 238 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def f():
reveal_type(a7) # revealed: None
reveal_type(a8) # revealed: Literal[1]
# TODO: This should be Color.RED
reveal_type(b1) # revealed: Literal[0]
reveal_type(b1) # revealed: Unknown | Literal[0]

# error: [invalid-type-form]
invalid1: Literal[3 + 4]
Expand Down
17 changes: 8 additions & 9 deletions crates/red_knot_python_semantic/resources/mdtest/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ class C:

reveal_type(C.pure_class_variable1) # revealed: str

# TODO: this should be `Literal[1]`, or `Unknown | Literal[1]`.
# TODO: Should be `Unknown | Literal[1]`.
reveal_type(C.pure_class_variable2) # revealed: Unknown

c_instance = C()
Expand Down Expand Up @@ -252,8 +252,7 @@ class C:

reveal_type(C.variable_with_class_default1) # revealed: str

# TODO: this should be `Unknown | Literal[1]`.
reveal_type(C.variable_with_class_default2) # revealed: Literal[1]
reveal_type(C.variable_with_class_default2) # revealed: Unknown | Literal[1]

c_instance = C()

Expand Down Expand Up @@ -296,8 +295,8 @@ def _(flag: bool):
else:
x = 4

reveal_type(C1.x) # revealed: Literal[1, 2]
reveal_type(C2.x) # revealed: Literal[3, 4]
reveal_type(C1.x) # revealed: Unknown | Literal[1, 2]
reveal_type(C2.x) # revealed: Unknown | Literal[3, 4]
```

## Inherited class attributes
Expand All @@ -311,7 +310,7 @@ class A:
class B(A): ...
class C(B): ...

reveal_type(C.X) # revealed: Literal["foo"]
reveal_type(C.X) # revealed: Unknown | Literal["foo"]
```

### Multiple inheritance
Expand All @@ -334,7 +333,7 @@ class A(B, C): ...
reveal_type(A.__mro__)

# `E` is earlier in the MRO than `F`, so we should use the type of `E.X`
reveal_type(A.X) # revealed: Literal[42]
reveal_type(A.X) # revealed: Unknown | Literal[42]
```

## Unions with possibly unbound paths
Expand All @@ -356,7 +355,7 @@ def _(flag1: bool, flag2: bool):
C = C1 if flag1 else C2 if flag2 else C3

# error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C1, C2, C3]` is possibly unbound"
reveal_type(C.x) # revealed: Literal[1, 3]
reveal_type(C.x) # revealed: Unknown | Literal[1, 3]
```

### Possibly-unbound within a class
Expand All @@ -379,7 +378,7 @@ def _(flag: bool, flag1: bool, flag2: bool):
C = C1 if flag1 else C2 if flag2 else C3

# error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C1, C2, C3]` is possibly unbound"
reveal_type(C.x) # revealed: Literal[1, 2, 3]
reveal_type(C.x) # revealed: Unknown | Literal[1, 2, 3]
```

### Unions with all paths unbound
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ reveal_type(b | b) # revealed: Literal[False]
## Arithmetic with a variable

```py
a = True
b = False
a: bool = True
b: bool = False

def lhs_is_int(x: int):
reveal_type(x + a) # revealed: int
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,8 @@ class A:
class B:
__add__ = A()

reveal_type(B() + B()) # revealed: int
# TODO: this could be `int` if we declare `B.__add__` using a `Callable` type
reveal_type(B() + B()) # revealed: Unknown | int
```

## Integration test: numbers from typeshed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ reveal_type(x) # revealed: int

### Undeclared but bound

We use the inferred type as the public type, if a symbol has no declared type.
We use the union of `Unknown` with the inferred type as the public type, if a symbol has no declared
type.

```py path=mod.py
x = 1
Expand All @@ -169,7 +170,7 @@ x = 1
```py
from mod import x

reveal_type(x) # revealed: Literal[1]
reveal_type(x) # revealed: Unknown | Literal[1]
```

### Undeclared and possibly unbound
Expand All @@ -189,7 +190,7 @@ if flag:
# on top of this document.
from mod import x

reveal_type(x) # revealed: Literal[1]
reveal_type(x) # revealed: Unknown | Literal[1]
```

### Undeclared and unbound
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class NonCallable:
__call__ = 1

a = NonCallable()
# error: "Object of type `NonCallable` is not callable"
# error: "Object of type `Unknown | Literal[1]` is not callable (due to union element `Literal[1]`)"
reveal_type(a()) # revealed: Unknown
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,29 @@
```py
def _(flag: bool):
class A:
always_bound = 1
always_bound: int = 1

if flag:
union = 1
else:
union = "abc"

if flag:
possibly_unbound = "abc"
union_declared: int = 1
else:
union_declared: str = "abc"

if flag:
possibly_unbound: str = "abc"

reveal_type(A.always_bound) # revealed: int

reveal_type(A.always_bound) # revealed: Literal[1]
reveal_type(A.union) # revealed: Unknown | Literal[1, "abc"]

reveal_type(A.union) # revealed: Literal[1, "abc"]
reveal_type(A.union_declared) # revealed: int | str

# error: [possibly-unbound-attribute] "Attribute `possibly_unbound` on type `Literal[A]` is possibly unbound"
reveal_type(A.possibly_unbound) # revealed: Literal["abc"]
reveal_type(A.possibly_unbound) # revealed: str

# error: [unresolved-attribute] "Type `Literal[A]` has no attribute `non_existent`"
reveal_type(A.non_existent) # revealed: Unknown
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ reveal_type("x" or "y" and "") # revealed: Literal["x"]
## Evaluates to builtin

```py path=a.py
redefined_builtin_bool = bool
redefined_builtin_bool: type[bool] = bool

def my_bool(x) -> bool:
return True
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,10 @@ class IntUnion:
def __len__(self) -> Literal[SomeEnum.INT, SomeEnum.INT_2]: ...

reveal_type(len(Auto())) # revealed: int
reveal_type(len(Int())) # revealed: Literal[2]
reveal_type(len(Int())) # revealed: int
reveal_type(len(Str())) # revealed: int
reveal_type(len(Tuple())) # revealed: int
reveal_type(len(IntUnion())) # revealed: Literal[2, 32]
reveal_type(len(IntUnion())) # revealed: int
```

### Negative integers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ wrong_innards: MyBox[int] = MyBox("five")
# TODO reveal int, do not leak the typevar
reveal_type(box.data) # revealed: T

reveal_type(MyBox.box_model_number) # revealed: Literal[695]
reveal_type(MyBox.box_model_number) # revealed: Unknown | Literal[695]
```

## Subclassing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ reveal_type(y)
# error: [possibly-unbound-import] "Member `y` of module `maybe_unbound` is possibly unbound"
from maybe_unbound import x, y

reveal_type(x) # revealed: Literal[3]
reveal_type(y) # revealed: Literal[3]
reveal_type(x) # revealed: Unknown | Literal[3]
reveal_type(y) # revealed: Unknown | Literal[3]
```

## Maybe unbound annotated
Expand Down Expand Up @@ -52,7 +52,7 @@ Importing an annotated name prefers the declared type over the inferred type:
# error: [possibly-unbound-import] "Member `y` of module `maybe_unbound_annotated` is possibly unbound"
from maybe_unbound_annotated import x, y

reveal_type(x) # revealed: Literal[3]
reveal_type(x) # revealed: Unknown | Literal[3]
reveal_type(y) # revealed: int
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ reveal_type(a.b) # revealed: <module 'a.b'>
```

```py path=a/__init__.py
b = 42
b: int = 42
```

```py path=a/b.py
Expand All @@ -20,11 +20,11 @@ b = 42
```py
from a import b

reveal_type(b) # revealed: Literal[42]
reveal_type(b) # revealed: int
```

```py path=a/__init__.py
b = 42
b: int = 42
```

```py path=a/b.py
Expand All @@ -41,7 +41,7 @@ reveal_type(a.b) # revealed: <module 'a.b'>
```

```py path=a/__init__.py
b = 42
b: int = 42
```

```py path=a/b.py
Expand All @@ -60,13 +60,13 @@ sees the submodule as the value of `b` instead of the integer.
from a import b
import a.b

# Python would say `Literal[42]` for `b`
# Python would say `int` for `b`
reveal_type(b) # revealed: <module 'a.b'>
reveal_type(a.b) # revealed: <module 'a.b'>
```

```py path=a/__init__.py
b = 42
b: int = 42
```

```py path=a/b.py
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ from a import b.c

# TODO: Should these be inferred as Unknown?
reveal_type(b) # revealed: <module 'a.b'>
reveal_type(b.c) # revealed: Literal[1]
reveal_type(b.c) # revealed: int
```

```py path=a/__init__.py
```

```py path=a/b.py
c = 1
c: int = 1
```
26 changes: 13 additions & 13 deletions crates/red_knot_python_semantic/resources/mdtest/import/relative.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ reveal_type(X) # revealed: Unknown
```

```py path=package/foo.py
X = 42
X: int = 42
```

```py path=package/bar.py
from .foo import X

reveal_type(X) # revealed: Literal[42]
reveal_type(X) # revealed: int
```

## Dotted
Expand All @@ -32,25 +32,25 @@ reveal_type(X) # revealed: Literal[42]
```

```py path=package/foo/bar/baz.py
X = 42
X: int = 42
```

```py path=package/bar.py
from .foo.bar.baz import X

reveal_type(X) # revealed: Literal[42]
reveal_type(X) # revealed: int
```

## Bare to package

```py path=package/__init__.py
X = 42
X: int = 42
```

```py path=package/bar.py
from . import X

reveal_type(X) # revealed: Literal[42]
reveal_type(X) # revealed: int
```

## Non-existent + bare to package
Expand All @@ -66,11 +66,11 @@ reveal_type(X) # revealed: Unknown
```py path=package/__init__.py
from .foo import X

reveal_type(X) # revealed: Literal[42]
reveal_type(X) # revealed: int
```

```py path=package/foo.py
X = 42
X: int = 42
```

## Non-existent + dunder init
Expand All @@ -87,13 +87,13 @@ reveal_type(X) # revealed: Unknown
```

```py path=package/foo.py
X = 42
X: int = 42
```

```py path=package/subpackage/subsubpackage/bar.py
from ...foo import X

reveal_type(X) # revealed: Literal[42]
reveal_type(X) # revealed: int
```

## Unbound symbol
Expand All @@ -117,13 +117,13 @@ reveal_type(x) # revealed: Unknown
```

```py path=package/foo.py
X = 42
X: int = 42
```

```py path=package/bar.py
from . import foo

reveal_type(foo.X) # revealed: Literal[42]
reveal_type(foo.X) # revealed: int
```

## Non-existent + bare to module
Expand Down Expand Up @@ -152,7 +152,7 @@ submodule via the attribute on its parent package.
```

```py path=package/foo.py
X = 42
X: int = 42
```

```py path=package/bar.py
Expand Down
Loading

0 comments on commit fd883fb

Please sign in to comment.