Compare commits
5 Commits
main
...
david/rena
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bacbae9680 | ||
|
|
8eca0b9e79 | ||
|
|
3117f5f7dc | ||
|
|
efc15aaecf | ||
|
|
0cf449efeb |
84
crates/ty_python_semantic/resources/mdtest/glossary.md
Normal file
84
crates/ty_python_semantic/resources/mdtest/glossary.md
Normal 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)
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
@@ -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():
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user