Skip to content
Merged
Changes from all commits
Commits
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 @@ -36,34 +36,26 @@ In particular, we should raise errors in the "possibly-undeclared-and-unbound" a
If a symbol has a declared type (`int`), we use that even if there is a more precise inferred type
(`Literal[1]`), or a conflicting inferred type (`str` vs. `Literal[2]` below):

`mod.py`:

```py
from typing import Any

def any() -> Any: ...

a: int = 1
b: str = 2 # error: [invalid-assignment]
c: Any = 3
d: int = any()
```

```py
from mod import a, b, c, d

reveal_type(a) # revealed: int
reveal_type(b) # revealed: str
reveal_type(c) # revealed: Any
reveal_type(d) # revealed: int
class Public:
a: int = 1
b: str = 2 # error: [invalid-assignment]
c: Any = 3
d: int = any()

reveal_type(Public.a) # revealed: int
reveal_type(Public.b) # revealed: str
reveal_type(Public.c) # revealed: Any
reveal_type(Public.d) # revealed: int
```

### Declared and possibly unbound

If a symbol is declared and *possibly* unbound, we trust that other module and use the declared type
without raising an error.

`mod.py`:
If a symbol is declared and *possibly* unbound, we trust the declared type without raising an error.

```py
from typing import Any
Expand All @@ -72,46 +64,38 @@ def any() -> Any: ...
def flag() -> bool:
return True

a: int
b: str
c: Any
d: int

if flag:
a = 1
b = 2 # error: [invalid-assignment]
c = 3
d = any()
```
class Public:
a: int
b: str
c: Any
d: int

```py
from mod import a, b, c, d
if flag:
a = 1
b = 2 # error: [invalid-assignment]
c = 3
d = any()

reveal_type(a) # revealed: int
reveal_type(b) # revealed: str
reveal_type(c) # revealed: Any
reveal_type(d) # revealed: int
reveal_type(Public.a) # revealed: int
reveal_type(Public.b) # revealed: str
reveal_type(Public.c) # revealed: Any
reveal_type(Public.d) # revealed: int
```

### Declared and unbound

Similarly, if a symbol is declared but unbound, we do not raise an error. We trust that this symbol
is available somehow and simply use the declared type.

`mod.py`:

```py
from typing import Any

a: int
b: Any
```

```py
from mod import a, b
class Public:
a: int
b: Any

reveal_type(a) # revealed: int
reveal_type(b) # revealed: Any
reveal_type(Public.a) # revealed: int
reveal_type(Public.b) # revealed: Any
```

## Possibly undeclared
Expand All @@ -121,37 +105,32 @@ reveal_type(b) # revealed: Any
If a symbol is possibly undeclared but definitely bound, we use the union of the declared and
inferred types:

`mod.py`:

```py
from typing import Any

def any() -> Any: ...
def flag() -> bool:
return True

a = 1
b = 2
c = 3
d = any()
if flag():
a: int
b: Any
c: str # error: [invalid-declaration]
d: int
```

```py
from mod import a, b, c, d
class Public:
a = 1
b = 2
c = 3
d = any()
if flag():
a: int
b: Any
c: str # error: [invalid-declaration]
d: int

reveal_type(a) # revealed: int
reveal_type(b) # revealed: Literal[2] | Any
reveal_type(c) # revealed: Literal[3] | Unknown
reveal_type(d) # revealed: Any | int
reveal_type(Public.a) # revealed: int
reveal_type(Public.b) # revealed: Literal[2] | Any
reveal_type(Public.c) # revealed: Literal[3] | Unknown
reveal_type(Public.d) # revealed: Any | int

# External modifications of `a` that violate the declared type are not allowed:
# error: [invalid-assignment]
a = None
Public.a = None
```

### Possibly undeclared and possibly unbound
Expand All @@ -161,59 +140,50 @@ inferred types. This case is interesting because the "possibly declared" definit
same as the "possibly bound" definition (symbol `b`). Note that we raise a `possibly-missing-import`
error for both `a` and `b`:

`mod.py`:

```py
from typing import Any

def flag() -> bool:
return True

if flag():
a: Any = 1
b = 2
else:
b: str
```
class Public:
if flag():
a: Any = 1
b = 2
else:
b: str

```py
# error: [possibly-missing-import] "Member `a` of module `mod` may be missing"
# error: [possibly-missing-import] "Member `b` of module `mod` may be missing"
from mod import a, b

reveal_type(a) # revealed: Literal[1] | Any
reveal_type(b) # revealed: Literal[2] | str
# error: [possibly-missing-attribute]
reveal_type(Public.a) # revealed: Literal[1] | Any
# error: [possibly-missing-attribute]
reveal_type(Public.b) # revealed: Literal[2] | str

# External modifications of `b` that violate the declared type are not allowed:
# error: [possibly-missing-attribute]
# error: [invalid-assignment]
b = None
Public.b = None
```

### Possibly undeclared and unbound

If a symbol is possibly undeclared and definitely unbound, we currently do not raise an error. This
seems inconsistent when compared to the case just above.

`mod.py`:

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

if flag():
a: int
```
class Public:
if flag():
a: int

```py
# TODO: this should raise an error. Once we fix this, update the section description and the table
# on top of this document.
from mod import a

reveal_type(a) # revealed: int
reveal_type(Public.a) # revealed: int

# External modifications to `a` that violate the declared type are not allowed:
# error: [invalid-assignment]
a = None
Public.a = None
```

## Undeclared
Expand All @@ -224,73 +194,59 @@ If a symbol is *undeclared*, we use the union of `Unknown` with the inferred typ
treat this case differently from the case where a symbol is implicitly declared with `Unknown`,
possibly due to the usage of an unknown name in the annotation:

`mod.py`:

```py
# Undeclared:
a = 1

# Implicitly declared with `Unknown`, due to the usage of an unknown name in the annotation:
b: SomeUnknownName = 1 # error: [unresolved-reference]
```
class Public:
# Undeclared:
a = 1

```py
from mod import a, b
# Implicitly declared with `Unknown`, due to the usage of an unknown name in the annotation:
b: SomeUnknownName = 1 # error: [unresolved-reference]

reveal_type(a) # revealed: Unknown | Literal[1]
reveal_type(b) # revealed: Unknown
reveal_type(Public.a) # revealed: Unknown | Literal[1]
reveal_type(Public.b) # revealed: Unknown

# All external modifications of `a` are allowed:
a = None
Public.a = None
```

### Undeclared and possibly unbound

If a symbol is undeclared and *possibly* unbound, we currently do not raise an error. This seems
inconsistent when compared to the "possibly-undeclared-and-possibly-unbound" case.

`mod.py`:

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

if flag:
a = 1
b: SomeUnknownName = 1 # error: [unresolved-reference]
```
class Public:
if flag:
a = 1
b: SomeUnknownName = 1 # error: [unresolved-reference]

```py
# TODO: this should raise an error. Once we fix this, update the section description and the table
# TODO: these should raise an error. Once we fix this, update the section description and the table
# on top of this document.
from mod import a, b

reveal_type(a) # revealed: Unknown | Literal[1]
reveal_type(b) # revealed: Unknown
reveal_type(Public.a) # revealed: Unknown | Literal[1]
reveal_type(Public.b) # revealed: Unknown

# All external modifications of `a` are allowed:
a = None
Public.a = None
```

### Undeclared and unbound

If a symbol is undeclared *and* unbound, we infer `Unknown` and raise an error.

`mod.py`:

```py
if False:
a: int = 1
```

```py
# error: [unresolved-import]
from mod import a
class Public:
if False:
a: int = 1

reveal_type(a) # revealed: Unknown
# error: [unresolved-attribute]
reveal_type(Public.a) # revealed: Unknown

# Modifications allowed in this case:
a = None
# Modification attempts yield an error:
# error: [unresolved-attribute]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only behavioral change. It makes sense that we emit an error for class scopes. For the module-global scope, "modifying a" was just creating a new variable.

Public.a = None
```

## In stub files
Expand Down
Loading