Compare commits

...

5 Commits

Author SHA1 Message Date
David Peter
bacbae9680 One more 2025-07-03 10:29:28 +02:00
David Peter
8eca0b9e79 public => nonlocal 2025-07-03 10:24:23 +02:00
David Peter
3117f5f7dc public_types.md => nonlocal_types.md 2025-07-03 10:23:40 +02:00
David Peter
efc15aaecf public => nonlocal 2025-07-03 10:21:43 +02:00
David Peter
0cf449efeb Add glossary 2025-07-03 10:20:44 +02:00
5 changed files with 98 additions and 13 deletions

View File

@@ -0,0 +1,84 @@
# Glossary
This document is meant to serve as a reference for ty developers, explaining the meaning of various
terms used in the codebase and documentation.
## Local type, public type, imported type, attribute type, nonlocal type
To understand the difference between these terms, consider the following example:
`module.py`:
```py
x_imported = 1
reveal_type(x_imported) # revealed: Literal[1]
x_imported = 2
reveal_type(x_imported) # revealed: Literal[2]
```
`main.py`:
```py
from module import x_imported
class C:
x_attribute = 1
x_attribute = 2
reveal_type(x_attribute) # revealed: Literal[2]
x_nonlocal = 1
def f():
x_local = 1
reveal_type(x_local) # revealed: Literal[1]
# All of the following are considered "public" types:
reveal_type(x_imported) # revealed: Unknown | Literal[2]
reveal_type(C.x_attribute) # revealed: Unknown | Literal[2]
reveal_type(x_nonlocal) # revealed: Unknown | Literal[1, 2]
x_local = 2
f()
x_nonlocal = 2
f()
```
The first distinction we make is between "local types" and "public types".
A **local type** refers to the type of a symbol that is defined and accessed within the same scope
(`x_local`). Local types are subject to flow-sensitive analysis: the type of `x_local` at the usage
site in `f` is `Literal[1]`, despite the fact that `x_local` is later reassigned to `2`.
In contrast, the **public type** of a symbol refers to the type that is visible to external scopes.
Since we can not generally analyze the full control flow of the program, we assume that these
symbols could be modified by code that is not currently visible to us. We therefore use the union
with `Unknown` for public uses of these symbols.
Public types can be further subdivided into:
- **Imported types**: The type of a symbol that is imported from another module. Notice how the
imported type of `x_imported` in `main.py` only includes `Literal[2]`, but not `Literal[1]`. The
imported type always refers to the local type that the symbol would have at the *end of the
scope* in which it was defined.
- **Attribute types**: The type of an attribute expression such as `C.x_attribute`. Similar to
imported types, the attribute type refers to the local type of the symbol at the *end of the
scope* in which it was defined.
- **Nonlocal types**: Refers to the type of a symbol that is defined in an *enclosing scope*. In
contrast to imported and attribute types, we consider *all reachable bindings* of the symbol in
the enclosing scope. This is needed in the example above because `f()` is called from different
positions in the enclosing scope. The nonlocal type therefore needs to include both `Literal[1]`
and `Literal[2]`.
To learn more, see:
- [Public type of undeclared symbols](doc/public_type_undeclared_symbols.md)
- [Nonlocal types](nonlocal_types.md)
- [Boundness and declaredness: public uses](boundness_declaredness/public.md)

View File

@@ -23,7 +23,7 @@ reveal_type(a.z) # revealed: Literal[0]
# Make sure that we infer the narrowed type for eager
# scopes (class, comprehension) and the non-narrowed
# public type for lazy scopes (function)
# nonlocal type for lazy scopes (function)
class _:
reveal_type(a.x) # revealed: Literal[0]
reveal_type(a.y) # revealed: Literal[0]

View File

@@ -1,11 +1,11 @@
# Public types
# Nonlocal types
## Basic
The "public type" of a symbol refers to the type that is inferred in a nested scope for a symbol
The "nonlocal type" of a symbol refers to the type that is inferred in a nested scope for a symbol
defined in an outer enclosing scope. Since it is not generally possible to analyze the full control
flow of a program, we currently make the simplifying assumption that an inner scope (such as the
`inner` function below) could be executed at any position in the enclosing scope. The public type
`inner` function below) could be executed at any position in the enclosing scope. The nonlocal type
should therefore be the union of all possible types that the symbol could have.
In the following example, depending on when `inner()` is called, the type of `x` could either be `A`
@@ -33,7 +33,8 @@ def outer() -> None:
inner()
```
Similarly, if control flow in the outer scope can split, the public type of `x` should reflect that:
Similarly, if control flow in the outer scope can split, the nonlocal type of `x` should reflect
that:
```py
def outer(flag: bool) -> None:
@@ -55,7 +56,7 @@ def outer(flag: bool) -> None:
inner()
```
If a binding is not reachable, it is not considered in the public type:
If a binding is not reachable, it is not considered in the nonlocal type:
```py
def outer() -> None:
@@ -117,8 +118,8 @@ def outer(flag: bool) -> None:
inner()
```
The public type is available, even if the end of the outer scope is unreachable. This is a
regression test. A previous version of ty used the end-of-scope position to determine the public
The nonlocal type is available, even if the end of the outer scope is unreachable. This is a
regression test. A previous version of ty used the end-of-scope position to determine the nonlocal
type, which would have resulted in incorrect type inference here:
```py
@@ -215,7 +216,7 @@ def _():
```
This pattern appears frequently with conditional imports. The `import` statement is both a
declaration and a binding, but we still add `None` to the public type union in a situation like
declaration and a binding, but we still add `None` to the nonlocal type union in a situation like
this:
```py

View File

@@ -241,9 +241,9 @@ def f():
### Use of variable in nested function
This is a regression test for a behavior that previously caused problems when the public type still
referred to the end-of-scope, which would result in an unresolved-reference error here since the end
of the scope is unreachable.
This is a regression test for a behavior that previously caused problems when the nonlocal type
still referred to the end-of-scope, which would result in an unresolved-reference error here since
the end of the scope is unreachable.
```py
def outer():

View File

@@ -46,7 +46,7 @@
//! resolving `f` at each of the three numbered positions will give you a `FunctionType`, which
//! wraps a `FunctionLiteral`, which contain `OverloadLiteral`s only for the definitions that
//! appear before that position. We rely on the fact that later definitions shadow earlier ones, so
//! the public type of `f` is resolved at position 3, correctly giving you all of the overloads
//! the final type of `f` is resolved at position 3, correctly giving you all of the overloads
//! (and the implementation).
use std::str::FromStr;