mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 02:38:25 +00:00
[ty] type narrowing by attribute/subscript assignments (#18041)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
## Summary This PR partially solves https://github.com/astral-sh/ty/issues/164 (derived from #17643). Currently, the definitions we manage are limited to those for simple name (symbol) targets, but we expand this to track definitions for attribute and subscript targets as well. This was originally planned as part of the work in #17643, but the changes are significant, so I made it a separate PR. After merging this PR, I will reflect this changes in #17643. There is still some incomplete work remaining, but the basic features have been implemented, so I am publishing it as a draft PR. Here is the TODO list (there may be more to come): * [x] Complete rewrite and refactoring of documentation (removing `Symbol` and replacing it with `Place`) * [x] More thorough testing * [x] Consolidation of duplicated code (maybe we can consolidate the handling related to name, attribute, and subscript) This PR replaces the current `Symbol` API with the `Place` API, which is a concept that includes attributes and subscripts (the term is borrowed from Rust). ## Test Plan `mdtest/narrow/assignment.md` is added. --------- Co-authored-by: David Peter <sharkdp@users.noreply.github.com> Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
parent
ce8b744f17
commit
0858896bc4
38 changed files with 3432 additions and 2404 deletions
|
@ -37,7 +37,9 @@ reveal_type(c_instance.inferred_from_other_attribute) # revealed: Unknown
|
|||
# See https://github.com/astral-sh/ruff/issues/15960 for a related discussion.
|
||||
reveal_type(c_instance.inferred_from_param) # revealed: Unknown | int | None
|
||||
|
||||
reveal_type(c_instance.declared_only) # revealed: bytes
|
||||
# TODO: Should be `bytes` with no error, like mypy and pyright?
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(c_instance.declared_only) # revealed: Unknown
|
||||
|
||||
reveal_type(c_instance.declared_and_bound) # revealed: bool
|
||||
|
||||
|
@ -64,12 +66,10 @@ C.inferred_from_value = "overwritten on class"
|
|||
# This assignment is fine:
|
||||
c_instance.declared_and_bound = False
|
||||
|
||||
# TODO: After this assignment to the attribute within this scope, we may eventually want to narrow
|
||||
# the `bool` type (see above) for this instance variable to `Literal[False]` here. This is unsound
|
||||
# in general (we don't know what else happened to `c_instance` between the assignment and the use
|
||||
# here), but mypy and pyright support this. In conclusion, this could be `bool` but should probably
|
||||
# be `Literal[False]`.
|
||||
reveal_type(c_instance.declared_and_bound) # revealed: bool
|
||||
# Strictly speaking, inferring this as `Literal[False]` rather than `bool` is unsound in general
|
||||
# (we don't know what else happened to `c_instance` between the assignment and the use here),
|
||||
# but mypy and pyright support this.
|
||||
reveal_type(c_instance.declared_and_bound) # revealed: Literal[False]
|
||||
```
|
||||
|
||||
#### Variable declared in class body and possibly bound in `__init__`
|
||||
|
@ -149,14 +149,16 @@ class C:
|
|||
c_instance = C(True)
|
||||
|
||||
reveal_type(c_instance.only_declared_in_body) # revealed: str | None
|
||||
reveal_type(c_instance.only_declared_in_init) # revealed: str | None
|
||||
# TODO: should be `str | None` without error
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(c_instance.only_declared_in_init) # revealed: Unknown
|
||||
reveal_type(c_instance.declared_in_body_and_init) # revealed: str | None
|
||||
|
||||
reveal_type(c_instance.declared_in_body_defined_in_init) # revealed: str | None
|
||||
|
||||
# TODO: This should be `str | None`. Fixing this requires an overhaul of the `Symbol` API,
|
||||
# which is planned in https://github.com/astral-sh/ruff/issues/14297
|
||||
reveal_type(c_instance.bound_in_body_declared_in_init) # revealed: Unknown | str | None
|
||||
reveal_type(c_instance.bound_in_body_declared_in_init) # revealed: Unknown | Literal["a"]
|
||||
|
||||
reveal_type(c_instance.bound_in_body_and_init) # revealed: Unknown | None | Literal["a"]
|
||||
```
|
||||
|
@ -187,7 +189,9 @@ reveal_type(c_instance.inferred_from_other_attribute) # revealed: Unknown
|
|||
|
||||
reveal_type(c_instance.inferred_from_param) # revealed: Unknown | int | None
|
||||
|
||||
reveal_type(c_instance.declared_only) # revealed: bytes
|
||||
# TODO: should be `bytes` with no error, like mypy and pyright?
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(c_instance.declared_only) # revealed: Unknown
|
||||
|
||||
reveal_type(c_instance.declared_and_bound) # revealed: bool
|
||||
|
||||
|
@ -260,8 +264,8 @@ class C:
|
|||
self.w += None
|
||||
|
||||
# TODO: Mypy and pyright do not support this, but it would be great if we could
|
||||
# infer `Unknown | str` or at least `Unknown | Weird | str` here.
|
||||
reveal_type(C().w) # revealed: Unknown | Weird
|
||||
# infer `Unknown | str` here (`Weird` is not a possible type for the `w` attribute).
|
||||
reveal_type(C().w) # revealed: Unknown
|
||||
```
|
||||
|
||||
#### Attributes defined in tuple unpackings
|
||||
|
@ -410,14 +414,41 @@ class C:
|
|||
[... for self.a in IntIterable()]
|
||||
[... for (self.b, self.c) in TupleIterable()]
|
||||
[... for self.d in IntIterable() for self.e in IntIterable()]
|
||||
[[... for self.f in IntIterable()] for _ in IntIterable()]
|
||||
[[... for self.g in IntIterable()] for self in [D()]]
|
||||
|
||||
class D:
|
||||
g: int
|
||||
|
||||
c_instance = C()
|
||||
|
||||
reveal_type(c_instance.a) # revealed: Unknown | int
|
||||
reveal_type(c_instance.b) # revealed: Unknown | int
|
||||
reveal_type(c_instance.c) # revealed: Unknown | str
|
||||
reveal_type(c_instance.d) # revealed: Unknown | int
|
||||
reveal_type(c_instance.e) # revealed: Unknown | int
|
||||
# TODO: no error, reveal Unknown | int
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(c_instance.a) # revealed: Unknown
|
||||
|
||||
# TODO: no error, reveal Unknown | int
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(c_instance.b) # revealed: Unknown
|
||||
|
||||
# TODO: no error, reveal Unknown | str
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(c_instance.c) # revealed: Unknown
|
||||
|
||||
# TODO: no error, reveal Unknown | int
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(c_instance.d) # revealed: Unknown
|
||||
|
||||
# TODO: no error, reveal Unknown | int
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(c_instance.e) # revealed: Unknown
|
||||
|
||||
# TODO: no error, reveal Unknown | int
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(c_instance.f) # revealed: Unknown
|
||||
|
||||
# This one is correctly not resolved as an attribute:
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(c_instance.g) # revealed: Unknown
|
||||
```
|
||||
|
||||
#### Conditionally declared / bound attributes
|
||||
|
@ -721,10 +752,7 @@ reveal_type(C.pure_class_variable) # revealed: Unknown
|
|||
# error: [invalid-attribute-access] "Cannot assign to instance attribute `pure_class_variable` from the class object `<class 'C'>`"
|
||||
C.pure_class_variable = "overwritten on class"
|
||||
|
||||
# TODO: should be `Unknown | Literal["value set in class method"]` or
|
||||
# Literal["overwritten on class"]`, once/if we support local narrowing.
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(C.pure_class_variable) # revealed: Unknown
|
||||
reveal_type(C.pure_class_variable) # revealed: Literal["overwritten on class"]
|
||||
|
||||
c_instance = C()
|
||||
reveal_type(c_instance.pure_class_variable) # revealed: Unknown | Literal["value set in class method"]
|
||||
|
@ -762,19 +790,12 @@ reveal_type(c_instance.variable_with_class_default2) # revealed: Unknown | Lite
|
|||
c_instance.variable_with_class_default1 = "value set on instance"
|
||||
|
||||
reveal_type(C.variable_with_class_default1) # revealed: str
|
||||
|
||||
# TODO: Could be Literal["value set on instance"], or still `str` if we choose not to
|
||||
# narrow the type.
|
||||
reveal_type(c_instance.variable_with_class_default1) # revealed: str
|
||||
reveal_type(c_instance.variable_with_class_default1) # revealed: Literal["value set on instance"]
|
||||
|
||||
C.variable_with_class_default1 = "overwritten on class"
|
||||
|
||||
# TODO: Could be `Literal["overwritten on class"]`, or still `str` if we choose not to
|
||||
# narrow the type.
|
||||
reveal_type(C.variable_with_class_default1) # revealed: str
|
||||
|
||||
# TODO: should still be `Literal["value set on instance"]`, or `str`.
|
||||
reveal_type(c_instance.variable_with_class_default1) # revealed: str
|
||||
reveal_type(C.variable_with_class_default1) # revealed: Literal["overwritten on class"]
|
||||
reveal_type(c_instance.variable_with_class_default1) # revealed: Literal["value set on instance"]
|
||||
```
|
||||
|
||||
#### Descriptor attributes as class variables
|
||||
|
|
|
@ -699,9 +699,7 @@ class C:
|
|||
descriptor = Descriptor()
|
||||
|
||||
C.descriptor = "something else"
|
||||
|
||||
# This could also be `Literal["something else"]` if we support narrowing of attribute types based on assignments
|
||||
reveal_type(C.descriptor) # revealed: Unknown | int
|
||||
reveal_type(C.descriptor) # revealed: Literal["something else"]
|
||||
```
|
||||
|
||||
### Possibly unbound descriptor attributes
|
||||
|
|
318
crates/ty_python_semantic/resources/mdtest/narrow/assignment.md
Normal file
318
crates/ty_python_semantic/resources/mdtest/narrow/assignment.md
Normal file
|
@ -0,0 +1,318 @@
|
|||
# Narrowing by assignment
|
||||
|
||||
## Attribute
|
||||
|
||||
### Basic
|
||||
|
||||
```py
|
||||
class A:
|
||||
x: int | None = None
|
||||
y = None
|
||||
|
||||
def __init__(self):
|
||||
self.z = None
|
||||
|
||||
a = A()
|
||||
a.x = 0
|
||||
a.y = 0
|
||||
a.z = 0
|
||||
|
||||
reveal_type(a.x) # revealed: Literal[0]
|
||||
reveal_type(a.y) # revealed: Literal[0]
|
||||
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)
|
||||
class _:
|
||||
reveal_type(a.x) # revealed: Literal[0]
|
||||
reveal_type(a.y) # revealed: Literal[0]
|
||||
reveal_type(a.z) # revealed: Literal[0]
|
||||
|
||||
[reveal_type(a.x) for _ in range(1)] # revealed: Literal[0]
|
||||
[reveal_type(a.y) for _ in range(1)] # revealed: Literal[0]
|
||||
[reveal_type(a.z) for _ in range(1)] # revealed: Literal[0]
|
||||
|
||||
def _():
|
||||
reveal_type(a.x) # revealed: Unknown | int | None
|
||||
reveal_type(a.y) # revealed: Unknown | None
|
||||
reveal_type(a.z) # revealed: Unknown | None
|
||||
|
||||
if False:
|
||||
a = A()
|
||||
reveal_type(a.x) # revealed: Literal[0]
|
||||
reveal_type(a.y) # revealed: Literal[0]
|
||||
reveal_type(a.z) # revealed: Literal[0]
|
||||
|
||||
if True:
|
||||
a = A()
|
||||
reveal_type(a.x) # revealed: int | None
|
||||
reveal_type(a.y) # revealed: Unknown | None
|
||||
reveal_type(a.z) # revealed: Unknown | None
|
||||
|
||||
a.x = 0
|
||||
a.y = 0
|
||||
a.z = 0
|
||||
reveal_type(a.x) # revealed: Literal[0]
|
||||
reveal_type(a.y) # revealed: Literal[0]
|
||||
reveal_type(a.z) # revealed: Literal[0]
|
||||
|
||||
class _:
|
||||
a = A()
|
||||
reveal_type(a.x) # revealed: int | None
|
||||
reveal_type(a.y) # revealed: Unknown | None
|
||||
reveal_type(a.z) # revealed: Unknown | None
|
||||
|
||||
def cond() -> bool:
|
||||
return True
|
||||
|
||||
class _:
|
||||
if False:
|
||||
a = A()
|
||||
reveal_type(a.x) # revealed: Literal[0]
|
||||
reveal_type(a.y) # revealed: Literal[0]
|
||||
reveal_type(a.z) # revealed: Literal[0]
|
||||
|
||||
if cond():
|
||||
a = A()
|
||||
reveal_type(a.x) # revealed: int | None
|
||||
reveal_type(a.y) # revealed: Unknown | None
|
||||
reveal_type(a.z) # revealed: Unknown | None
|
||||
|
||||
class _:
|
||||
a = A()
|
||||
|
||||
class Inner:
|
||||
reveal_type(a.x) # revealed: int | None
|
||||
reveal_type(a.y) # revealed: Unknown | None
|
||||
reveal_type(a.z) # revealed: Unknown | None
|
||||
|
||||
# error: [unresolved-reference]
|
||||
does.nt.exist = 0
|
||||
# error: [unresolved-reference]
|
||||
reveal_type(does.nt.exist) # revealed: Unknown
|
||||
```
|
||||
|
||||
### Narrowing chain
|
||||
|
||||
```py
|
||||
class D: ...
|
||||
|
||||
class C:
|
||||
d: D | None = None
|
||||
|
||||
class B:
|
||||
c1: C | None = None
|
||||
c2: C | None = None
|
||||
|
||||
class A:
|
||||
b: B | None = None
|
||||
|
||||
a = A()
|
||||
a.b = B()
|
||||
a.b.c1 = C()
|
||||
a.b.c2 = C()
|
||||
a.b.c1.d = D()
|
||||
a.b.c2.d = D()
|
||||
reveal_type(a.b) # revealed: B
|
||||
reveal_type(a.b.c1) # revealed: C
|
||||
reveal_type(a.b.c1.d) # revealed: D
|
||||
|
||||
a.b.c1 = C()
|
||||
reveal_type(a.b) # revealed: B
|
||||
reveal_type(a.b.c1) # revealed: C
|
||||
reveal_type(a.b.c1.d) # revealed: D | None
|
||||
reveal_type(a.b.c2.d) # revealed: D
|
||||
|
||||
a.b.c1.d = D()
|
||||
a.b = B()
|
||||
reveal_type(a.b) # revealed: B
|
||||
reveal_type(a.b.c1) # revealed: C | None
|
||||
reveal_type(a.b.c2) # revealed: C | None
|
||||
# error: [possibly-unbound-attribute]
|
||||
reveal_type(a.b.c1.d) # revealed: D | None
|
||||
# error: [possibly-unbound-attribute]
|
||||
reveal_type(a.b.c2.d) # revealed: D | None
|
||||
```
|
||||
|
||||
### Do not narrow the type of a `property` by assignment
|
||||
|
||||
```py
|
||||
class C:
|
||||
def __init__(self):
|
||||
self._x: int = 0
|
||||
|
||||
@property
|
||||
def x(self) -> int:
|
||||
return self._x
|
||||
|
||||
@x.setter
|
||||
def x(self, value: int) -> None:
|
||||
self._x = abs(value)
|
||||
|
||||
c = C()
|
||||
c.x = -1
|
||||
# Don't infer `c.x` to be `Literal[-1]`
|
||||
reveal_type(c.x) # revealed: int
|
||||
```
|
||||
|
||||
### Do not narrow the type of a descriptor by assignment
|
||||
|
||||
```py
|
||||
class Descriptor:
|
||||
def __get__(self, instance: object, owner: type) -> int:
|
||||
return 1
|
||||
|
||||
def __set__(self, instance: object, value: int) -> None:
|
||||
pass
|
||||
|
||||
class C:
|
||||
desc: Descriptor = Descriptor()
|
||||
|
||||
c = C()
|
||||
c.desc = -1
|
||||
# Don't infer `c.desc` to be `Literal[-1]`
|
||||
reveal_type(c.desc) # revealed: int
|
||||
```
|
||||
|
||||
## Subscript
|
||||
|
||||
### Specialization for builtin types
|
||||
|
||||
Type narrowing based on assignment to a subscript expression is generally unsound, because arbitrary
|
||||
`__getitem__`/`__setitem__` methods on a class do not necessarily guarantee that the passed-in value
|
||||
for `__setitem__` is stored and can be retrieved unmodified via `__getitem__`. Therefore, we
|
||||
currently only perform assignment-based narrowing on a few built-in classes (`list`, `dict`,
|
||||
`bytesarray`, `TypedDict` and `collections` types) where we are confident that this kind of
|
||||
narrowing can be performed soundly. This is the same approach as pyright.
|
||||
|
||||
```py
|
||||
from typing import TypedDict
|
||||
from collections import ChainMap, defaultdict
|
||||
|
||||
l: list[int | None] = [None]
|
||||
l[0] = 0
|
||||
d: dict[int, int] = {1: 1}
|
||||
d[0] = 0
|
||||
b: bytearray = bytearray(b"abc")
|
||||
b[0] = 0
|
||||
dd: defaultdict[int, int] = defaultdict(int)
|
||||
dd[0] = 0
|
||||
cm: ChainMap[int, int] = ChainMap({1: 1}, {0: 0})
|
||||
cm[0] = 0
|
||||
# TODO: should be ChainMap[int, int]
|
||||
reveal_type(cm) # revealed: ChainMap[Unknown, Unknown]
|
||||
|
||||
reveal_type(l[0]) # revealed: Literal[0]
|
||||
reveal_type(d[0]) # revealed: Literal[0]
|
||||
reveal_type(b[0]) # revealed: Literal[0]
|
||||
reveal_type(dd[0]) # revealed: Literal[0]
|
||||
# TODO: should be Literal[0]
|
||||
reveal_type(cm[0]) # revealed: Unknown
|
||||
|
||||
class C:
|
||||
reveal_type(l[0]) # revealed: Literal[0]
|
||||
reveal_type(d[0]) # revealed: Literal[0]
|
||||
reveal_type(b[0]) # revealed: Literal[0]
|
||||
reveal_type(dd[0]) # revealed: Literal[0]
|
||||
# TODO: should be Literal[0]
|
||||
reveal_type(cm[0]) # revealed: Unknown
|
||||
|
||||
[reveal_type(l[0]) for _ in range(1)] # revealed: Literal[0]
|
||||
[reveal_type(d[0]) for _ in range(1)] # revealed: Literal[0]
|
||||
[reveal_type(b[0]) for _ in range(1)] # revealed: Literal[0]
|
||||
[reveal_type(dd[0]) for _ in range(1)] # revealed: Literal[0]
|
||||
# TODO: should be Literal[0]
|
||||
[reveal_type(cm[0]) for _ in range(1)] # revealed: Unknown
|
||||
|
||||
def _():
|
||||
reveal_type(l[0]) # revealed: int | None
|
||||
reveal_type(d[0]) # revealed: int
|
||||
reveal_type(b[0]) # revealed: int
|
||||
reveal_type(dd[0]) # revealed: int
|
||||
reveal_type(cm[0]) # revealed: int
|
||||
|
||||
class D(TypedDict):
|
||||
x: int
|
||||
label: str
|
||||
|
||||
td = D(x=1, label="a")
|
||||
td["x"] = 0
|
||||
# TODO: should be Literal[0]
|
||||
reveal_type(td["x"]) # revealed: @Todo(TypedDict)
|
||||
|
||||
# error: [unresolved-reference]
|
||||
does["not"]["exist"] = 0
|
||||
# error: [unresolved-reference]
|
||||
reveal_type(does["not"]["exist"]) # revealed: Unknown
|
||||
|
||||
non_subscriptable = 1
|
||||
# error: [non-subscriptable]
|
||||
non_subscriptable[0] = 0
|
||||
# error: [non-subscriptable]
|
||||
reveal_type(non_subscriptable[0]) # revealed: Unknown
|
||||
```
|
||||
|
||||
### No narrowing for custom classes with arbitrary `__getitem__` / `__setitem__`
|
||||
|
||||
```py
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.l: list[str] = []
|
||||
|
||||
def __getitem__(self, index: int) -> str:
|
||||
return self.l[index]
|
||||
|
||||
def __setitem__(self, index: int, value: str | int) -> None:
|
||||
if len(self.l) == index:
|
||||
self.l.append(str(value))
|
||||
else:
|
||||
self.l[index] = str(value)
|
||||
|
||||
c = C()
|
||||
c[0] = 0
|
||||
reveal_type(c[0]) # revealed: str
|
||||
```
|
||||
|
||||
## Complex target
|
||||
|
||||
```py
|
||||
class A:
|
||||
x: list[int | None] = []
|
||||
|
||||
class B:
|
||||
a: A | None = None
|
||||
|
||||
b = B()
|
||||
b.a = A()
|
||||
b.a.x[0] = 0
|
||||
|
||||
reveal_type(b.a.x[0]) # revealed: Literal[0]
|
||||
|
||||
class C:
|
||||
reveal_type(b.a.x[0]) # revealed: Literal[0]
|
||||
|
||||
def _():
|
||||
# error: [possibly-unbound-attribute]
|
||||
reveal_type(b.a.x[0]) # revealed: Unknown | int | None
|
||||
# error: [possibly-unbound-attribute]
|
||||
reveal_type(b.a.x) # revealed: Unknown | list[int | None]
|
||||
reveal_type(b.a) # revealed: Unknown | A | None
|
||||
```
|
||||
|
||||
## Invalid assignments are not used for narrowing
|
||||
|
||||
```py
|
||||
class C:
|
||||
x: int | None
|
||||
l: list[int]
|
||||
|
||||
def f(c: C, s: str):
|
||||
c.x = s # error: [invalid-assignment]
|
||||
reveal_type(c.x) # revealed: int | None
|
||||
s = c.x # error: [invalid-assignment]
|
||||
|
||||
# TODO: This assignment is invalid and should result in an error.
|
||||
c.l[0] = s
|
||||
reveal_type(c.l[0]) # revealed: int
|
||||
```
|
|
@ -53,11 +53,114 @@ constraints may no longer be valid due to a "time lag". However, it may be possi
|
|||
that some of them are valid by performing a more detailed analysis (e.g. checking that the narrowing
|
||||
target has not changed in all places where the function is called).
|
||||
|
||||
### Narrowing by attribute/subscript assignments
|
||||
|
||||
```py
|
||||
class A:
|
||||
x: str | None = None
|
||||
|
||||
def update_x(self, value: str | None):
|
||||
self.x = value
|
||||
|
||||
a = A()
|
||||
a.x = "a"
|
||||
|
||||
class B:
|
||||
reveal_type(a.x) # revealed: Literal["a"]
|
||||
|
||||
def f():
|
||||
reveal_type(a.x) # revealed: Unknown | str | None
|
||||
|
||||
[reveal_type(a.x) for _ in range(1)] # revealed: Literal["a"]
|
||||
|
||||
a = A()
|
||||
|
||||
class C:
|
||||
reveal_type(a.x) # revealed: str | None
|
||||
|
||||
def g():
|
||||
reveal_type(a.x) # revealed: Unknown | str | None
|
||||
|
||||
[reveal_type(a.x) for _ in range(1)] # revealed: str | None
|
||||
|
||||
a = A()
|
||||
a.x = "a"
|
||||
a.update_x("b")
|
||||
|
||||
class D:
|
||||
# TODO: should be `str | None`
|
||||
reveal_type(a.x) # revealed: Literal["a"]
|
||||
|
||||
def h():
|
||||
reveal_type(a.x) # revealed: Unknown | str | None
|
||||
|
||||
# TODO: should be `str | None`
|
||||
[reveal_type(a.x) for _ in range(1)] # revealed: Literal["a"]
|
||||
```
|
||||
|
||||
### Narrowing by attribute/subscript assignments in nested scopes
|
||||
|
||||
```py
|
||||
class D: ...
|
||||
|
||||
class C:
|
||||
d: D | None = None
|
||||
|
||||
class B:
|
||||
c1: C | None = None
|
||||
c2: C | None = None
|
||||
|
||||
class A:
|
||||
b: B | None = None
|
||||
|
||||
a = A()
|
||||
a.b = B()
|
||||
|
||||
class _:
|
||||
a.b.c1 = C()
|
||||
|
||||
class _:
|
||||
a.b.c1.d = D()
|
||||
a = 1
|
||||
|
||||
class _3:
|
||||
reveal_type(a) # revealed: A
|
||||
reveal_type(a.b.c1.d) # revealed: D
|
||||
|
||||
class _:
|
||||
a = 1
|
||||
# error: [unresolved-attribute]
|
||||
a.b.c1.d = D()
|
||||
|
||||
class _3:
|
||||
reveal_type(a) # revealed: A
|
||||
# TODO: should be `D | None`
|
||||
reveal_type(a.b.c1.d) # revealed: D
|
||||
|
||||
a.b.c1 = C()
|
||||
a.b.c1.d = D()
|
||||
|
||||
class _:
|
||||
a.b = B()
|
||||
|
||||
class _:
|
||||
# error: [possibly-unbound-attribute]
|
||||
reveal_type(a.b.c1.d) # revealed: D | None
|
||||
reveal_type(a.b.c1) # revealed: C | None
|
||||
```
|
||||
|
||||
### Narrowing constraints introduced in eager nested scopes
|
||||
|
||||
```py
|
||||
g: str | None = "a"
|
||||
|
||||
class A:
|
||||
x: str | None = None
|
||||
|
||||
a = A()
|
||||
|
||||
l: list[str | None] = [None]
|
||||
|
||||
def f(x: str | None):
|
||||
def _():
|
||||
if x is not None:
|
||||
|
@ -69,6 +172,14 @@ def f(x: str | None):
|
|||
if g is not None:
|
||||
reveal_type(g) # revealed: str
|
||||
|
||||
if a.x is not None:
|
||||
# TODO(#17643): should be `Unknown | str`
|
||||
reveal_type(a.x) # revealed: Unknown | str | None
|
||||
|
||||
if l[0] is not None:
|
||||
# TODO(#17643): should be `str`
|
||||
reveal_type(l[0]) # revealed: str | None
|
||||
|
||||
class C:
|
||||
if x is not None:
|
||||
reveal_type(x) # revealed: str
|
||||
|
@ -79,6 +190,14 @@ def f(x: str | None):
|
|||
if g is not None:
|
||||
reveal_type(g) # revealed: str
|
||||
|
||||
if a.x is not None:
|
||||
# TODO(#17643): should be `Unknown | str`
|
||||
reveal_type(a.x) # revealed: Unknown | str | None
|
||||
|
||||
if l[0] is not None:
|
||||
# TODO(#17643): should be `str`
|
||||
reveal_type(l[0]) # revealed: str | None
|
||||
|
||||
# TODO: should be str
|
||||
# This could be fixed if we supported narrowing with if clauses in comprehensions.
|
||||
[reveal_type(x) for _ in range(1) if x is not None] # revealed: str | None
|
||||
|
@ -89,6 +208,13 @@ def f(x: str | None):
|
|||
```py
|
||||
g: str | None = "a"
|
||||
|
||||
class A:
|
||||
x: str | None = None
|
||||
|
||||
a = A()
|
||||
|
||||
l: list[str | None] = [None]
|
||||
|
||||
def f(x: str | None):
|
||||
if x is not None:
|
||||
def _():
|
||||
|
@ -109,6 +235,28 @@ def f(x: str | None):
|
|||
reveal_type(g) # revealed: str
|
||||
|
||||
[reveal_type(g) for _ in range(1)] # revealed: str
|
||||
|
||||
if a.x is not None:
|
||||
def _():
|
||||
reveal_type(a.x) # revealed: Unknown | str | None
|
||||
|
||||
class D:
|
||||
# TODO(#17643): should be `Unknown | str`
|
||||
reveal_type(a.x) # revealed: Unknown | str | None
|
||||
|
||||
# TODO(#17643): should be `Unknown | str`
|
||||
[reveal_type(a.x) for _ in range(1)] # revealed: Unknown | str | None
|
||||
|
||||
if l[0] is not None:
|
||||
def _():
|
||||
reveal_type(l[0]) # revealed: str | None
|
||||
|
||||
class D:
|
||||
# TODO(#17643): should be `str`
|
||||
reveal_type(l[0]) # revealed: str | None
|
||||
|
||||
# TODO(#17643): should be `str`
|
||||
[reveal_type(l[0]) for _ in range(1)] # revealed: str | None
|
||||
```
|
||||
|
||||
### Narrowing constraints introduced in multiple scopes
|
||||
|
@ -118,6 +266,13 @@ from typing import Literal
|
|||
|
||||
g: str | Literal[1] | None = "a"
|
||||
|
||||
class A:
|
||||
x: str | Literal[1] | None = None
|
||||
|
||||
a = A()
|
||||
|
||||
l: list[str | Literal[1] | None] = [None]
|
||||
|
||||
def f(x: str | Literal[1] | None):
|
||||
class C:
|
||||
if x is not None:
|
||||
|
@ -140,6 +295,28 @@ def f(x: str | Literal[1] | None):
|
|||
class D:
|
||||
if g != 1:
|
||||
reveal_type(g) # revealed: str
|
||||
|
||||
if a.x is not None:
|
||||
def _():
|
||||
if a.x != 1:
|
||||
# TODO(#17643): should be `Unknown | str | None`
|
||||
reveal_type(a.x) # revealed: Unknown | str | Literal[1] | None
|
||||
|
||||
class D:
|
||||
if a.x != 1:
|
||||
# TODO(#17643): should be `Unknown | str`
|
||||
reveal_type(a.x) # revealed: Unknown | str | Literal[1] | None
|
||||
|
||||
if l[0] is not None:
|
||||
def _():
|
||||
if l[0] != 1:
|
||||
# TODO(#17643): should be `str | None`
|
||||
reveal_type(l[0]) # revealed: str | Literal[1] | None
|
||||
|
||||
class D:
|
||||
if l[0] != 1:
|
||||
# TODO(#17643): should be `str`
|
||||
reveal_type(l[0]) # revealed: str | Literal[1] | None
|
||||
```
|
||||
|
||||
### Narrowing constraints with bindings in class scope, and nested scopes
|
||||
|
|
|
@ -7,7 +7,7 @@ use ruff_python_ast::statement_visitor::{StatementVisitor, walk_stmt};
|
|||
use ruff_python_ast::{self as ast};
|
||||
|
||||
use crate::semantic_index::ast_ids::HasScopedExpressionId;
|
||||
use crate::semantic_index::symbol::ScopeId;
|
||||
use crate::semantic_index::place::ScopeId;
|
||||
use crate::semantic_index::{SemanticIndex, global_scope, semantic_index};
|
||||
use crate::types::{Truthiness, Type, infer_expression_types};
|
||||
use crate::{Db, ModuleName, resolve_module};
|
||||
|
|
|
@ -24,13 +24,13 @@ pub(crate) mod list;
|
|||
mod module_name;
|
||||
mod module_resolver;
|
||||
mod node_key;
|
||||
pub(crate) mod place;
|
||||
mod program;
|
||||
mod python_platform;
|
||||
pub mod semantic_index;
|
||||
mod semantic_model;
|
||||
pub(crate) mod site_packages;
|
||||
mod suppression;
|
||||
pub(crate) mod symbol;
|
||||
pub mod types;
|
||||
mod unpack;
|
||||
mod util;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -19,9 +19,9 @@ use crate::semantic_index::builder::SemanticIndexBuilder;
|
|||
use crate::semantic_index::definition::{Definition, DefinitionNodeKey, Definitions};
|
||||
use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::narrowing_constraints::ScopedNarrowingConstraint;
|
||||
use crate::semantic_index::symbol::{
|
||||
FileScopeId, NodeWithScopeKey, NodeWithScopeRef, Scope, ScopeId, ScopeKind, ScopedSymbolId,
|
||||
SymbolTable,
|
||||
use crate::semantic_index::place::{
|
||||
FileScopeId, NodeWithScopeKey, NodeWithScopeRef, PlaceExpr, PlaceTable, Scope, ScopeId,
|
||||
ScopeKind, ScopedPlaceId,
|
||||
};
|
||||
use crate::semantic_index::use_def::{EagerSnapshotKey, ScopedEagerSnapshotId, UseDefMap};
|
||||
|
||||
|
@ -30,9 +30,9 @@ mod builder;
|
|||
pub mod definition;
|
||||
pub mod expression;
|
||||
pub(crate) mod narrowing_constraints;
|
||||
pub mod place;
|
||||
pub(crate) mod predicate;
|
||||
mod re_exports;
|
||||
pub mod symbol;
|
||||
mod use_def;
|
||||
mod visibility_constraints;
|
||||
|
||||
|
@ -41,7 +41,7 @@ pub(crate) use self::use_def::{
|
|||
DeclarationsIterator,
|
||||
};
|
||||
|
||||
type SymbolMap = hashbrown::HashMap<ScopedSymbolId, (), FxBuildHasher>;
|
||||
type PlaceSet = hashbrown::HashMap<ScopedPlaceId, (), FxBuildHasher>;
|
||||
|
||||
/// Returns the semantic index for `file`.
|
||||
///
|
||||
|
@ -55,18 +55,18 @@ pub(crate) fn semantic_index(db: &dyn Db, file: File) -> SemanticIndex<'_> {
|
|||
SemanticIndexBuilder::new(db, file, parsed).build()
|
||||
}
|
||||
|
||||
/// Returns the symbol table for a specific `scope`.
|
||||
/// Returns the place table for a specific `scope`.
|
||||
///
|
||||
/// Using [`symbol_table`] over [`semantic_index`] has the advantage that
|
||||
/// Salsa can avoid invalidating dependent queries if this scope's symbol table
|
||||
/// Using [`place_table`] over [`semantic_index`] has the advantage that
|
||||
/// Salsa can avoid invalidating dependent queries if this scope's place table
|
||||
/// is unchanged.
|
||||
#[salsa::tracked(returns(deref))]
|
||||
pub(crate) fn symbol_table<'db>(db: &'db dyn Db, scope: ScopeId<'db>) -> Arc<SymbolTable> {
|
||||
pub(crate) fn place_table<'db>(db: &'db dyn Db, scope: ScopeId<'db>) -> Arc<PlaceTable> {
|
||||
let file = scope.file(db);
|
||||
let _span = tracing::trace_span!("symbol_table", scope=?scope.as_id(), ?file).entered();
|
||||
let _span = tracing::trace_span!("place_table", scope=?scope.as_id(), ?file).entered();
|
||||
let index = semantic_index(db, file);
|
||||
|
||||
index.symbol_table(scope.file_scope_id(db))
|
||||
index.place_table(scope.file_scope_id(db))
|
||||
}
|
||||
|
||||
/// Returns the set of modules that are imported anywhere in `file`.
|
||||
|
@ -113,13 +113,10 @@ pub(crate) fn attribute_assignments<'db, 's>(
|
|||
let index = semantic_index(db, file);
|
||||
|
||||
attribute_scopes(db, class_body_scope).filter_map(|function_scope_id| {
|
||||
let attribute_table = index.instance_attribute_table(function_scope_id);
|
||||
let symbol = attribute_table.symbol_id_by_name(name)?;
|
||||
let place_table = index.place_table(function_scope_id);
|
||||
let place = place_table.place_id_by_instance_attribute_name(name)?;
|
||||
let use_def = &index.use_def_maps[function_scope_id];
|
||||
Some((
|
||||
use_def.instance_attribute_bindings(symbol),
|
||||
function_scope_id,
|
||||
))
|
||||
Some((use_def.public_bindings(place), function_scope_id))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -167,14 +164,11 @@ pub(crate) enum EagerSnapshotResult<'map, 'db> {
|
|||
NoLongerInEagerContext,
|
||||
}
|
||||
|
||||
/// The symbol tables and use-def maps for all scopes in a file.
|
||||
/// The place tables and use-def maps for all scopes in a file.
|
||||
#[derive(Debug, Update)]
|
||||
pub(crate) struct SemanticIndex<'db> {
|
||||
/// List of all symbol tables in this file, indexed by scope.
|
||||
symbol_tables: IndexVec<FileScopeId, Arc<SymbolTable>>,
|
||||
|
||||
/// List of all instance attribute tables in this file, indexed by scope.
|
||||
instance_attribute_tables: IndexVec<FileScopeId, SymbolTable>,
|
||||
/// List of all place tables in this file, indexed by scope.
|
||||
place_tables: IndexVec<FileScopeId, Arc<PlaceTable>>,
|
||||
|
||||
/// List of all scopes in this file.
|
||||
scopes: IndexVec<FileScopeId, Scope>,
|
||||
|
@ -195,7 +189,7 @@ pub(crate) struct SemanticIndex<'db> {
|
|||
scope_ids_by_scope: IndexVec<FileScopeId, ScopeId<'db>>,
|
||||
|
||||
/// Map from the file-local [`FileScopeId`] to the set of explicit-global symbols it contains.
|
||||
globals_by_scope: FxHashMap<FileScopeId, FxHashSet<ScopedSymbolId>>,
|
||||
globals_by_scope: FxHashMap<FileScopeId, FxHashSet<ScopedPlaceId>>,
|
||||
|
||||
/// Use-def map for each scope in this file.
|
||||
use_def_maps: IndexVec<FileScopeId, Arc<UseDefMap<'db>>>,
|
||||
|
@ -223,17 +217,13 @@ pub(crate) struct SemanticIndex<'db> {
|
|||
}
|
||||
|
||||
impl<'db> SemanticIndex<'db> {
|
||||
/// Returns the symbol table for a specific scope.
|
||||
/// Returns the place table for a specific scope.
|
||||
///
|
||||
/// Use the Salsa cached [`symbol_table()`] query if you only need the
|
||||
/// symbol table for a single scope.
|
||||
/// Use the Salsa cached [`place_table()`] query if you only need the
|
||||
/// place table for a single scope.
|
||||
#[track_caller]
|
||||
pub(super) fn symbol_table(&self, scope_id: FileScopeId) -> Arc<SymbolTable> {
|
||||
self.symbol_tables[scope_id].clone()
|
||||
}
|
||||
|
||||
pub(super) fn instance_attribute_table(&self, scope_id: FileScopeId) -> &SymbolTable {
|
||||
&self.instance_attribute_tables[scope_id]
|
||||
pub(super) fn place_table(&self, scope_id: FileScopeId) -> Arc<PlaceTable> {
|
||||
self.place_tables[scope_id].clone()
|
||||
}
|
||||
|
||||
/// Returns the use-def map for a specific scope.
|
||||
|
@ -286,7 +276,7 @@ impl<'db> SemanticIndex<'db> {
|
|||
|
||||
pub(crate) fn symbol_is_global_in_scope(
|
||||
&self,
|
||||
symbol: ScopedSymbolId,
|
||||
symbol: ScopedPlaceId,
|
||||
scope: FileScopeId,
|
||||
) -> bool {
|
||||
self.globals_by_scope
|
||||
|
@ -444,7 +434,7 @@ impl<'db> SemanticIndex<'db> {
|
|||
pub(crate) fn eager_snapshot(
|
||||
&self,
|
||||
enclosing_scope: FileScopeId,
|
||||
symbol: &str,
|
||||
expr: &PlaceExpr,
|
||||
nested_scope: FileScopeId,
|
||||
) -> EagerSnapshotResult<'_, 'db> {
|
||||
for (ancestor_scope_id, ancestor_scope) in self.ancestor_scopes(nested_scope) {
|
||||
|
@ -455,12 +445,12 @@ impl<'db> SemanticIndex<'db> {
|
|||
return EagerSnapshotResult::NoLongerInEagerContext;
|
||||
}
|
||||
}
|
||||
let Some(symbol_id) = self.symbol_tables[enclosing_scope].symbol_id_by_name(symbol) else {
|
||||
let Some(place_id) = self.place_tables[enclosing_scope].place_id_by_expr(expr) else {
|
||||
return EagerSnapshotResult::NotFound;
|
||||
};
|
||||
let key = EagerSnapshotKey {
|
||||
enclosing_scope,
|
||||
enclosing_symbol: symbol_id,
|
||||
enclosing_place: place_id,
|
||||
nested_scope,
|
||||
};
|
||||
let Some(id) = self.eager_snapshots.get(&key) else {
|
||||
|
@ -480,9 +470,9 @@ pub struct AncestorsIter<'a> {
|
|||
}
|
||||
|
||||
impl<'a> AncestorsIter<'a> {
|
||||
fn new(module_symbol_table: &'a SemanticIndex, start: FileScopeId) -> Self {
|
||||
fn new(module_table: &'a SemanticIndex, start: FileScopeId) -> Self {
|
||||
Self {
|
||||
scopes: &module_symbol_table.scopes,
|
||||
scopes: &module_table.scopes,
|
||||
next_id: Some(start),
|
||||
}
|
||||
}
|
||||
|
@ -508,9 +498,9 @@ pub struct DescendantsIter<'a> {
|
|||
}
|
||||
|
||||
impl<'a> DescendantsIter<'a> {
|
||||
fn new(symbol_table: &'a SemanticIndex, scope_id: FileScopeId) -> Self {
|
||||
let scope = &symbol_table.scopes[scope_id];
|
||||
let scopes = &symbol_table.scopes[scope.descendants()];
|
||||
fn new(index: &'a SemanticIndex, scope_id: FileScopeId) -> Self {
|
||||
let scope = &index.scopes[scope_id];
|
||||
let scopes = &index.scopes[scope.descendants()];
|
||||
|
||||
Self {
|
||||
next_id: scope_id + 1,
|
||||
|
@ -545,8 +535,8 @@ pub struct ChildrenIter<'a> {
|
|||
}
|
||||
|
||||
impl<'a> ChildrenIter<'a> {
|
||||
pub(crate) fn new(module_symbol_table: &'a SemanticIndex, parent: FileScopeId) -> Self {
|
||||
let descendants = DescendantsIter::new(module_symbol_table, parent);
|
||||
pub(crate) fn new(module_index: &'a SemanticIndex, parent: FileScopeId) -> Self {
|
||||
let descendants = DescendantsIter::new(module_index, parent);
|
||||
|
||||
Self {
|
||||
parent,
|
||||
|
@ -577,21 +567,19 @@ mod tests {
|
|||
use crate::db::tests::{TestDb, TestDbBuilder};
|
||||
use crate::semantic_index::ast_ids::{HasScopedUseId, ScopedUseId};
|
||||
use crate::semantic_index::definition::{Definition, DefinitionKind};
|
||||
use crate::semantic_index::symbol::{
|
||||
FileScopeId, Scope, ScopeKind, ScopedSymbolId, SymbolTable,
|
||||
};
|
||||
use crate::semantic_index::place::{FileScopeId, PlaceTable, Scope, ScopeKind, ScopedPlaceId};
|
||||
use crate::semantic_index::use_def::UseDefMap;
|
||||
use crate::semantic_index::{global_scope, semantic_index, symbol_table, use_def_map};
|
||||
use crate::semantic_index::{global_scope, place_table, semantic_index, use_def_map};
|
||||
|
||||
impl UseDefMap<'_> {
|
||||
fn first_public_binding(&self, symbol: ScopedSymbolId) -> Option<Definition<'_>> {
|
||||
fn first_public_binding(&self, symbol: ScopedPlaceId) -> Option<Definition<'_>> {
|
||||
self.public_bindings(symbol)
|
||||
.find_map(|constrained_binding| constrained_binding.binding)
|
||||
.find_map(|constrained_binding| constrained_binding.binding.definition())
|
||||
}
|
||||
|
||||
fn first_binding_at_use(&self, use_id: ScopedUseId) -> Option<Definition<'_>> {
|
||||
self.bindings_at_use(use_id)
|
||||
.find_map(|constrained_binding| constrained_binding.binding)
|
||||
.find_map(|constrained_binding| constrained_binding.binding.definition())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -613,17 +601,17 @@ mod tests {
|
|||
TestCase { db, file }
|
||||
}
|
||||
|
||||
fn names(table: &SymbolTable) -> Vec<String> {
|
||||
fn names(table: &PlaceTable) -> Vec<String> {
|
||||
table
|
||||
.symbols()
|
||||
.map(|symbol| symbol.name().to_string())
|
||||
.places()
|
||||
.filter_map(|expr| Some(expr.as_name()?.to_string()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
let TestCase { db, file } = test_case("");
|
||||
let global_table = symbol_table(&db, global_scope(&db, file));
|
||||
let global_table = place_table(&db, global_scope(&db, file));
|
||||
|
||||
let global_names = names(global_table);
|
||||
|
||||
|
@ -633,7 +621,7 @@ mod tests {
|
|||
#[test]
|
||||
fn simple() {
|
||||
let TestCase { db, file } = test_case("x");
|
||||
let global_table = symbol_table(&db, global_scope(&db, file));
|
||||
let global_table = place_table(&db, global_scope(&db, file));
|
||||
|
||||
assert_eq!(names(global_table), vec!["x"]);
|
||||
}
|
||||
|
@ -641,7 +629,7 @@ mod tests {
|
|||
#[test]
|
||||
fn annotation_only() {
|
||||
let TestCase { db, file } = test_case("x: int");
|
||||
let global_table = symbol_table(&db, global_scope(&db, file));
|
||||
let global_table = place_table(&db, global_scope(&db, file));
|
||||
|
||||
assert_eq!(names(global_table), vec!["int", "x"]);
|
||||
// TODO record definition
|
||||
|
@ -651,10 +639,10 @@ mod tests {
|
|||
fn import() {
|
||||
let TestCase { db, file } = test_case("import foo");
|
||||
let scope = global_scope(&db, file);
|
||||
let global_table = symbol_table(&db, scope);
|
||||
let global_table = place_table(&db, scope);
|
||||
|
||||
assert_eq!(names(global_table), vec!["foo"]);
|
||||
let foo = global_table.symbol_id_by_name("foo").unwrap();
|
||||
let foo = global_table.place_id_by_name("foo").unwrap();
|
||||
|
||||
let use_def = use_def_map(&db, scope);
|
||||
let binding = use_def.first_public_binding(foo).unwrap();
|
||||
|
@ -664,7 +652,7 @@ mod tests {
|
|||
#[test]
|
||||
fn import_sub() {
|
||||
let TestCase { db, file } = test_case("import foo.bar");
|
||||
let global_table = symbol_table(&db, global_scope(&db, file));
|
||||
let global_table = place_table(&db, global_scope(&db, file));
|
||||
|
||||
assert_eq!(names(global_table), vec!["foo"]);
|
||||
}
|
||||
|
@ -672,7 +660,7 @@ mod tests {
|
|||
#[test]
|
||||
fn import_as() {
|
||||
let TestCase { db, file } = test_case("import foo.bar as baz");
|
||||
let global_table = symbol_table(&db, global_scope(&db, file));
|
||||
let global_table = place_table(&db, global_scope(&db, file));
|
||||
|
||||
assert_eq!(names(global_table), vec!["baz"]);
|
||||
}
|
||||
|
@ -681,12 +669,12 @@ mod tests {
|
|||
fn import_from() {
|
||||
let TestCase { db, file } = test_case("from bar import foo");
|
||||
let scope = global_scope(&db, file);
|
||||
let global_table = symbol_table(&db, scope);
|
||||
let global_table = place_table(&db, scope);
|
||||
|
||||
assert_eq!(names(global_table), vec!["foo"]);
|
||||
assert!(
|
||||
global_table
|
||||
.symbol_by_name("foo")
|
||||
.place_by_name("foo")
|
||||
.is_some_and(|symbol| { symbol.is_bound() && !symbol.is_used() }),
|
||||
"symbols that are defined get the defined flag"
|
||||
);
|
||||
|
@ -695,7 +683,7 @@ mod tests {
|
|||
let binding = use_def
|
||||
.first_public_binding(
|
||||
global_table
|
||||
.symbol_id_by_name("foo")
|
||||
.place_id_by_name("foo")
|
||||
.expect("symbol to exist"),
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -706,18 +694,18 @@ mod tests {
|
|||
fn assign() {
|
||||
let TestCase { db, file } = test_case("x = foo");
|
||||
let scope = global_scope(&db, file);
|
||||
let global_table = symbol_table(&db, scope);
|
||||
let global_table = place_table(&db, scope);
|
||||
|
||||
assert_eq!(names(global_table), vec!["foo", "x"]);
|
||||
assert!(
|
||||
global_table
|
||||
.symbol_by_name("foo")
|
||||
.place_by_name("foo")
|
||||
.is_some_and(|symbol| { !symbol.is_bound() && symbol.is_used() }),
|
||||
"a symbol used but not bound in a scope should have only the used flag"
|
||||
);
|
||||
let use_def = use_def_map(&db, scope);
|
||||
let binding = use_def
|
||||
.first_public_binding(global_table.symbol_id_by_name("x").expect("symbol exists"))
|
||||
.first_public_binding(global_table.place_id_by_name("x").expect("symbol exists"))
|
||||
.unwrap();
|
||||
assert!(matches!(binding.kind(&db), DefinitionKind::Assignment(_)));
|
||||
}
|
||||
|
@ -726,13 +714,13 @@ mod tests {
|
|||
fn augmented_assignment() {
|
||||
let TestCase { db, file } = test_case("x += 1");
|
||||
let scope = global_scope(&db, file);
|
||||
let global_table = symbol_table(&db, scope);
|
||||
let global_table = place_table(&db, scope);
|
||||
|
||||
assert_eq!(names(global_table), vec!["x"]);
|
||||
|
||||
let use_def = use_def_map(&db, scope);
|
||||
let binding = use_def
|
||||
.first_public_binding(global_table.symbol_id_by_name("x").unwrap())
|
||||
.first_public_binding(global_table.place_id_by_name("x").unwrap())
|
||||
.unwrap();
|
||||
|
||||
assert!(matches!(
|
||||
|
@ -750,7 +738,7 @@ class C:
|
|||
y = 2
|
||||
",
|
||||
);
|
||||
let global_table = symbol_table(&db, global_scope(&db, file));
|
||||
let global_table = place_table(&db, global_scope(&db, file));
|
||||
|
||||
assert_eq!(names(global_table), vec!["C", "y"]);
|
||||
|
||||
|
@ -765,12 +753,12 @@ y = 2
|
|||
assert_eq!(class_scope.kind(), ScopeKind::Class);
|
||||
assert_eq!(class_scope_id.to_scope_id(&db, file).name(&db), "C");
|
||||
|
||||
let class_table = index.symbol_table(class_scope_id);
|
||||
let class_table = index.place_table(class_scope_id);
|
||||
assert_eq!(names(&class_table), vec!["x"]);
|
||||
|
||||
let use_def = index.use_def_map(class_scope_id);
|
||||
let binding = use_def
|
||||
.first_public_binding(class_table.symbol_id_by_name("x").expect("symbol exists"))
|
||||
.first_public_binding(class_table.place_id_by_name("x").expect("symbol exists"))
|
||||
.unwrap();
|
||||
assert!(matches!(binding.kind(&db), DefinitionKind::Assignment(_)));
|
||||
}
|
||||
|
@ -785,7 +773,7 @@ y = 2
|
|||
",
|
||||
);
|
||||
let index = semantic_index(&db, file);
|
||||
let global_table = index.symbol_table(FileScopeId::global());
|
||||
let global_table = index.place_table(FileScopeId::global());
|
||||
|
||||
assert_eq!(names(&global_table), vec!["func", "y"]);
|
||||
|
||||
|
@ -798,16 +786,12 @@ y = 2
|
|||
assert_eq!(function_scope.kind(), ScopeKind::Function);
|
||||
assert_eq!(function_scope_id.to_scope_id(&db, file).name(&db), "func");
|
||||
|
||||
let function_table = index.symbol_table(function_scope_id);
|
||||
let function_table = index.place_table(function_scope_id);
|
||||
assert_eq!(names(&function_table), vec!["x"]);
|
||||
|
||||
let use_def = index.use_def_map(function_scope_id);
|
||||
let binding = use_def
|
||||
.first_public_binding(
|
||||
function_table
|
||||
.symbol_id_by_name("x")
|
||||
.expect("symbol exists"),
|
||||
)
|
||||
.first_public_binding(function_table.place_id_by_name("x").expect("symbol exists"))
|
||||
.unwrap();
|
||||
assert!(matches!(binding.kind(&db), DefinitionKind::Assignment(_)));
|
||||
}
|
||||
|
@ -822,7 +806,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
|||
);
|
||||
|
||||
let index = semantic_index(&db, file);
|
||||
let global_table = symbol_table(&db, global_scope(&db, file));
|
||||
let global_table = place_table(&db, global_scope(&db, file));
|
||||
|
||||
assert_eq!(names(global_table), vec!["str", "int", "f"]);
|
||||
|
||||
|
@ -833,7 +817,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
|||
panic!("Expected a function scope")
|
||||
};
|
||||
|
||||
let function_table = index.symbol_table(function_scope_id);
|
||||
let function_table = index.place_table(function_scope_id);
|
||||
assert_eq!(
|
||||
names(&function_table),
|
||||
vec!["a", "b", "c", "d", "args", "kwargs"],
|
||||
|
@ -844,7 +828,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
|||
let binding = use_def
|
||||
.first_public_binding(
|
||||
function_table
|
||||
.symbol_id_by_name(name)
|
||||
.place_id_by_name(name)
|
||||
.expect("symbol exists"),
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -853,7 +837,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
|||
let args_binding = use_def
|
||||
.first_public_binding(
|
||||
function_table
|
||||
.symbol_id_by_name("args")
|
||||
.place_id_by_name("args")
|
||||
.expect("symbol exists"),
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -864,7 +848,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
|||
let kwargs_binding = use_def
|
||||
.first_public_binding(
|
||||
function_table
|
||||
.symbol_id_by_name("kwargs")
|
||||
.place_id_by_name("kwargs")
|
||||
.expect("symbol exists"),
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -879,7 +863,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
|||
let TestCase { db, file } = test_case("lambda a, b, c=1, *args, d=2, **kwargs: None");
|
||||
|
||||
let index = semantic_index(&db, file);
|
||||
let global_table = symbol_table(&db, global_scope(&db, file));
|
||||
let global_table = place_table(&db, global_scope(&db, file));
|
||||
|
||||
assert!(names(global_table).is_empty());
|
||||
|
||||
|
@ -890,7 +874,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
|||
panic!("Expected a lambda scope")
|
||||
};
|
||||
|
||||
let lambda_table = index.symbol_table(lambda_scope_id);
|
||||
let lambda_table = index.place_table(lambda_scope_id);
|
||||
assert_eq!(
|
||||
names(&lambda_table),
|
||||
vec!["a", "b", "c", "d", "args", "kwargs"],
|
||||
|
@ -899,14 +883,14 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
|||
let use_def = index.use_def_map(lambda_scope_id);
|
||||
for name in ["a", "b", "c", "d"] {
|
||||
let binding = use_def
|
||||
.first_public_binding(lambda_table.symbol_id_by_name(name).expect("symbol exists"))
|
||||
.first_public_binding(lambda_table.place_id_by_name(name).expect("symbol exists"))
|
||||
.unwrap();
|
||||
assert!(matches!(binding.kind(&db), DefinitionKind::Parameter(_)));
|
||||
}
|
||||
let args_binding = use_def
|
||||
.first_public_binding(
|
||||
lambda_table
|
||||
.symbol_id_by_name("args")
|
||||
.place_id_by_name("args")
|
||||
.expect("symbol exists"),
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -917,7 +901,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
|||
let kwargs_binding = use_def
|
||||
.first_public_binding(
|
||||
lambda_table
|
||||
.symbol_id_by_name("kwargs")
|
||||
.place_id_by_name("kwargs")
|
||||
.expect("symbol exists"),
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -938,7 +922,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
|||
);
|
||||
|
||||
let index = semantic_index(&db, file);
|
||||
let global_table = index.symbol_table(FileScopeId::global());
|
||||
let global_table = index.place_table(FileScopeId::global());
|
||||
|
||||
assert_eq!(names(&global_table), vec!["iter1"]);
|
||||
|
||||
|
@ -955,7 +939,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
|||
"<listcomp>"
|
||||
);
|
||||
|
||||
let comprehension_symbol_table = index.symbol_table(comprehension_scope_id);
|
||||
let comprehension_symbol_table = index.place_table(comprehension_scope_id);
|
||||
|
||||
assert_eq!(names(&comprehension_symbol_table), vec!["x", "y"]);
|
||||
|
||||
|
@ -964,7 +948,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
|||
let binding = use_def
|
||||
.first_public_binding(
|
||||
comprehension_symbol_table
|
||||
.symbol_id_by_name(name)
|
||||
.place_id_by_name(name)
|
||||
.expect("symbol exists"),
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -1031,7 +1015,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
|||
);
|
||||
|
||||
let index = semantic_index(&db, file);
|
||||
let global_table = index.symbol_table(FileScopeId::global());
|
||||
let global_table = index.place_table(FileScopeId::global());
|
||||
|
||||
assert_eq!(names(&global_table), vec!["iter1"]);
|
||||
|
||||
|
@ -1048,7 +1032,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
|||
"<listcomp>"
|
||||
);
|
||||
|
||||
let comprehension_symbol_table = index.symbol_table(comprehension_scope_id);
|
||||
let comprehension_symbol_table = index.place_table(comprehension_scope_id);
|
||||
|
||||
assert_eq!(names(&comprehension_symbol_table), vec!["y", "iter2"]);
|
||||
|
||||
|
@ -1067,7 +1051,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
|||
"<setcomp>"
|
||||
);
|
||||
|
||||
let inner_comprehension_symbol_table = index.symbol_table(inner_comprehension_scope_id);
|
||||
let inner_comprehension_symbol_table = index.place_table(inner_comprehension_scope_id);
|
||||
|
||||
assert_eq!(names(&inner_comprehension_symbol_table), vec!["x"]);
|
||||
}
|
||||
|
@ -1082,14 +1066,14 @@ with item1 as x, item2 as y:
|
|||
);
|
||||
|
||||
let index = semantic_index(&db, file);
|
||||
let global_table = index.symbol_table(FileScopeId::global());
|
||||
let global_table = index.place_table(FileScopeId::global());
|
||||
|
||||
assert_eq!(names(&global_table), vec!["item1", "x", "item2", "y"]);
|
||||
|
||||
let use_def = index.use_def_map(FileScopeId::global());
|
||||
for name in ["x", "y"] {
|
||||
let binding = use_def
|
||||
.first_public_binding(global_table.symbol_id_by_name(name).expect("symbol exists"))
|
||||
.first_public_binding(global_table.place_id_by_name(name).expect("symbol exists"))
|
||||
.expect("Expected with item definition for {name}");
|
||||
assert!(matches!(binding.kind(&db), DefinitionKind::WithItem(_)));
|
||||
}
|
||||
|
@ -1105,14 +1089,14 @@ with context() as (x, y):
|
|||
);
|
||||
|
||||
let index = semantic_index(&db, file);
|
||||
let global_table = index.symbol_table(FileScopeId::global());
|
||||
let global_table = index.place_table(FileScopeId::global());
|
||||
|
||||
assert_eq!(names(&global_table), vec!["context", "x", "y"]);
|
||||
|
||||
let use_def = index.use_def_map(FileScopeId::global());
|
||||
for name in ["x", "y"] {
|
||||
let binding = use_def
|
||||
.first_public_binding(global_table.symbol_id_by_name(name).expect("symbol exists"))
|
||||
.first_public_binding(global_table.place_id_by_name(name).expect("symbol exists"))
|
||||
.expect("Expected with item definition for {name}");
|
||||
assert!(matches!(binding.kind(&db), DefinitionKind::WithItem(_)));
|
||||
}
|
||||
|
@ -1129,7 +1113,7 @@ def func():
|
|||
",
|
||||
);
|
||||
let index = semantic_index(&db, file);
|
||||
let global_table = index.symbol_table(FileScopeId::global());
|
||||
let global_table = index.place_table(FileScopeId::global());
|
||||
|
||||
assert_eq!(names(&global_table), vec!["func"]);
|
||||
let [
|
||||
|
@ -1148,8 +1132,8 @@ def func():
|
|||
assert_eq!(func_scope_2.kind(), ScopeKind::Function);
|
||||
assert_eq!(func_scope2_id.to_scope_id(&db, file).name(&db), "func");
|
||||
|
||||
let func1_table = index.symbol_table(func_scope1_id);
|
||||
let func2_table = index.symbol_table(func_scope2_id);
|
||||
let func1_table = index.place_table(func_scope1_id);
|
||||
let func2_table = index.place_table(func_scope2_id);
|
||||
assert_eq!(names(&func1_table), vec!["x"]);
|
||||
assert_eq!(names(&func2_table), vec!["y"]);
|
||||
|
||||
|
@ -1157,7 +1141,7 @@ def func():
|
|||
let binding = use_def
|
||||
.first_public_binding(
|
||||
global_table
|
||||
.symbol_id_by_name("func")
|
||||
.place_id_by_name("func")
|
||||
.expect("symbol exists"),
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -1174,7 +1158,7 @@ def func[T]():
|
|||
);
|
||||
|
||||
let index = semantic_index(&db, file);
|
||||
let global_table = index.symbol_table(FileScopeId::global());
|
||||
let global_table = index.place_table(FileScopeId::global());
|
||||
|
||||
assert_eq!(names(&global_table), vec!["func"]);
|
||||
|
||||
|
@ -1187,7 +1171,7 @@ def func[T]():
|
|||
|
||||
assert_eq!(ann_scope.kind(), ScopeKind::Annotation);
|
||||
assert_eq!(ann_scope_id.to_scope_id(&db, file).name(&db), "func");
|
||||
let ann_table = index.symbol_table(ann_scope_id);
|
||||
let ann_table = index.place_table(ann_scope_id);
|
||||
assert_eq!(names(&ann_table), vec!["T"]);
|
||||
|
||||
let [(func_scope_id, func_scope)] =
|
||||
|
@ -1197,7 +1181,7 @@ def func[T]():
|
|||
};
|
||||
assert_eq!(func_scope.kind(), ScopeKind::Function);
|
||||
assert_eq!(func_scope_id.to_scope_id(&db, file).name(&db), "func");
|
||||
let func_table = index.symbol_table(func_scope_id);
|
||||
let func_table = index.place_table(func_scope_id);
|
||||
assert_eq!(names(&func_table), vec!["x"]);
|
||||
}
|
||||
|
||||
|
@ -1211,7 +1195,7 @@ class C[T]:
|
|||
);
|
||||
|
||||
let index = semantic_index(&db, file);
|
||||
let global_table = index.symbol_table(FileScopeId::global());
|
||||
let global_table = index.place_table(FileScopeId::global());
|
||||
|
||||
assert_eq!(names(&global_table), vec!["C"]);
|
||||
|
||||
|
@ -1224,11 +1208,11 @@ class C[T]:
|
|||
|
||||
assert_eq!(ann_scope.kind(), ScopeKind::Annotation);
|
||||
assert_eq!(ann_scope_id.to_scope_id(&db, file).name(&db), "C");
|
||||
let ann_table = index.symbol_table(ann_scope_id);
|
||||
let ann_table = index.place_table(ann_scope_id);
|
||||
assert_eq!(names(&ann_table), vec!["T"]);
|
||||
assert!(
|
||||
ann_table
|
||||
.symbol_by_name("T")
|
||||
.place_by_name("T")
|
||||
.is_some_and(|s| s.is_bound() && !s.is_used()),
|
||||
"type parameters are defined by the scope that introduces them"
|
||||
);
|
||||
|
@ -1241,7 +1225,7 @@ class C[T]:
|
|||
|
||||
assert_eq!(class_scope.kind(), ScopeKind::Class);
|
||||
assert_eq!(class_scope_id.to_scope_id(&db, file).name(&db), "C");
|
||||
assert_eq!(names(&index.symbol_table(class_scope_id)), vec!["x"]);
|
||||
assert_eq!(names(&index.place_table(class_scope_id)), vec!["x"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1369,9 +1353,9 @@ match subject:
|
|||
);
|
||||
|
||||
let global_scope_id = global_scope(&db, file);
|
||||
let global_table = symbol_table(&db, global_scope_id);
|
||||
let global_table = place_table(&db, global_scope_id);
|
||||
|
||||
assert!(global_table.symbol_by_name("Foo").unwrap().is_used());
|
||||
assert!(global_table.place_by_name("Foo").unwrap().is_used());
|
||||
assert_eq!(
|
||||
names(global_table),
|
||||
vec![
|
||||
|
@ -1395,7 +1379,7 @@ match subject:
|
|||
("l", 1),
|
||||
] {
|
||||
let binding = use_def
|
||||
.first_public_binding(global_table.symbol_id_by_name(name).expect("symbol exists"))
|
||||
.first_public_binding(global_table.place_id_by_name(name).expect("symbol exists"))
|
||||
.expect("Expected with item definition for {name}");
|
||||
if let DefinitionKind::MatchPattern(pattern) = binding.kind(&db) {
|
||||
assert_eq!(pattern.index(), expected_index);
|
||||
|
@ -1418,14 +1402,14 @@ match 1:
|
|||
);
|
||||
|
||||
let global_scope_id = global_scope(&db, file);
|
||||
let global_table = symbol_table(&db, global_scope_id);
|
||||
let global_table = place_table(&db, global_scope_id);
|
||||
|
||||
assert_eq!(names(global_table), vec!["first", "second"]);
|
||||
|
||||
let use_def = use_def_map(&db, global_scope_id);
|
||||
for (name, expected_index) in [("first", 0), ("second", 0)] {
|
||||
let binding = use_def
|
||||
.first_public_binding(global_table.symbol_id_by_name(name).expect("symbol exists"))
|
||||
.first_public_binding(global_table.place_id_by_name(name).expect("symbol exists"))
|
||||
.expect("Expected with item definition for {name}");
|
||||
if let DefinitionKind::MatchPattern(pattern) = binding.kind(&db) {
|
||||
assert_eq!(pattern.index(), expected_index);
|
||||
|
@ -1439,13 +1423,13 @@ match 1:
|
|||
fn for_loops_single_assignment() {
|
||||
let TestCase { db, file } = test_case("for x in a: pass");
|
||||
let scope = global_scope(&db, file);
|
||||
let global_table = symbol_table(&db, scope);
|
||||
let global_table = place_table(&db, scope);
|
||||
|
||||
assert_eq!(&names(global_table), &["a", "x"]);
|
||||
|
||||
let use_def = use_def_map(&db, scope);
|
||||
let binding = use_def
|
||||
.first_public_binding(global_table.symbol_id_by_name("x").unwrap())
|
||||
.first_public_binding(global_table.place_id_by_name("x").unwrap())
|
||||
.unwrap();
|
||||
|
||||
assert!(matches!(binding.kind(&db), DefinitionKind::For(_)));
|
||||
|
@ -1455,16 +1439,16 @@ match 1:
|
|||
fn for_loops_simple_unpacking() {
|
||||
let TestCase { db, file } = test_case("for (x, y) in a: pass");
|
||||
let scope = global_scope(&db, file);
|
||||
let global_table = symbol_table(&db, scope);
|
||||
let global_table = place_table(&db, scope);
|
||||
|
||||
assert_eq!(&names(global_table), &["a", "x", "y"]);
|
||||
|
||||
let use_def = use_def_map(&db, scope);
|
||||
let x_binding = use_def
|
||||
.first_public_binding(global_table.symbol_id_by_name("x").unwrap())
|
||||
.first_public_binding(global_table.place_id_by_name("x").unwrap())
|
||||
.unwrap();
|
||||
let y_binding = use_def
|
||||
.first_public_binding(global_table.symbol_id_by_name("y").unwrap())
|
||||
.first_public_binding(global_table.place_id_by_name("y").unwrap())
|
||||
.unwrap();
|
||||
|
||||
assert!(matches!(x_binding.kind(&db), DefinitionKind::For(_)));
|
||||
|
@ -1475,13 +1459,13 @@ match 1:
|
|||
fn for_loops_complex_unpacking() {
|
||||
let TestCase { db, file } = test_case("for [((a,) b), (c, d)] in e: pass");
|
||||
let scope = global_scope(&db, file);
|
||||
let global_table = symbol_table(&db, scope);
|
||||
let global_table = place_table(&db, scope);
|
||||
|
||||
assert_eq!(&names(global_table), &["e", "a", "b", "c", "d"]);
|
||||
|
||||
let use_def = use_def_map(&db, scope);
|
||||
let binding = use_def
|
||||
.first_public_binding(global_table.symbol_id_by_name("a").unwrap())
|
||||
.first_public_binding(global_table.place_id_by_name("a").unwrap())
|
||||
.unwrap();
|
||||
|
||||
assert!(matches!(binding.kind(&db), DefinitionKind::For(_)));
|
||||
|
|
|
@ -6,14 +6,14 @@ use ruff_python_ast::ExprRef;
|
|||
|
||||
use crate::Db;
|
||||
use crate::semantic_index::ast_ids::node_key::ExpressionNodeKey;
|
||||
use crate::semantic_index::place::ScopeId;
|
||||
use crate::semantic_index::semantic_index;
|
||||
use crate::semantic_index::symbol::ScopeId;
|
||||
|
||||
/// AST ids for a single scope.
|
||||
///
|
||||
/// The motivation for building the AST ids per scope isn't about reducing invalidation because
|
||||
/// the struct changes whenever the parsed AST changes. Instead, it's mainly that we can
|
||||
/// build the AST ids struct when building the symbol table and also keep the property that
|
||||
/// build the AST ids struct when building the place table and also keep the property that
|
||||
/// IDs of outer scopes are unaffected by changes in inner scopes.
|
||||
///
|
||||
/// For example, we don't want that adding new statements to `foo` changes the statement id of `x = foo()` in:
|
||||
|
@ -28,7 +28,7 @@ use crate::semantic_index::symbol::ScopeId;
|
|||
pub(crate) struct AstIds {
|
||||
/// Maps expressions to their expression id.
|
||||
expressions_map: FxHashMap<ExpressionNodeKey, ScopedExpressionId>,
|
||||
/// Maps expressions which "use" a symbol (that is, [`ast::ExprName`]) to a use id.
|
||||
/// Maps expressions which "use" a place (that is, [`ast::ExprName`], [`ast::ExprAttribute`] or [`ast::ExprSubscript`]) to a use id.
|
||||
uses_map: FxHashMap<ExpressionNodeKey, ScopedUseId>,
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ fn ast_ids<'db>(db: &'db dyn Db, scope: ScopeId) -> &'db AstIds {
|
|||
semantic_index(db, scope.file(db)).ast_ids(scope.file_scope_id(db))
|
||||
}
|
||||
|
||||
/// Uniquely identifies a use of a name in a [`crate::semantic_index::symbol::FileScopeId`].
|
||||
/// Uniquely identifies a use of a name in a [`crate::semantic_index::place::FileScopeId`].
|
||||
#[newtype_index]
|
||||
pub struct ScopedUseId;
|
||||
|
||||
|
@ -72,6 +72,20 @@ impl HasScopedUseId for ast::ExprName {
|
|||
}
|
||||
}
|
||||
|
||||
impl HasScopedUseId for ast::ExprAttribute {
|
||||
fn scoped_use_id(&self, db: &dyn Db, scope: ScopeId) -> ScopedUseId {
|
||||
let expression_ref = ExprRef::from(self);
|
||||
expression_ref.scoped_use_id(db, scope)
|
||||
}
|
||||
}
|
||||
|
||||
impl HasScopedUseId for ast::ExprSubscript {
|
||||
fn scoped_use_id(&self, db: &dyn Db, scope: ScopeId) -> ScopedUseId {
|
||||
let expression_ref = ExprRef::from(self);
|
||||
expression_ref.scoped_use_id(db, scope)
|
||||
}
|
||||
}
|
||||
|
||||
impl HasScopedUseId for ast::ExprRef<'_> {
|
||||
fn scoped_use_id(&self, db: &dyn Db, scope: ScopeId) -> ScopedUseId {
|
||||
let ast_ids = ast_ids(db, scope);
|
||||
|
@ -79,7 +93,7 @@ impl HasScopedUseId for ast::ExprRef<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Uniquely identifies an [`ast::Expr`] in a [`crate::semantic_index::symbol::FileScopeId`].
|
||||
/// Uniquely identifies an [`ast::Expr`] in a [`crate::semantic_index::place::FileScopeId`].
|
||||
#[newtype_index]
|
||||
#[derive(salsa::Update)]
|
||||
pub struct ScopedExpressionId;
|
||||
|
|
|
@ -24,24 +24,22 @@ use crate::semantic_index::SemanticIndex;
|
|||
use crate::semantic_index::ast_ids::AstIdsBuilder;
|
||||
use crate::semantic_index::ast_ids::node_key::ExpressionNodeKey;
|
||||
use crate::semantic_index::definition::{
|
||||
AnnotatedAssignmentDefinitionKind, AnnotatedAssignmentDefinitionNodeRef,
|
||||
AssignmentDefinitionKind, AssignmentDefinitionNodeRef, ComprehensionDefinitionKind,
|
||||
ComprehensionDefinitionNodeRef, Definition, DefinitionCategory, DefinitionKind,
|
||||
DefinitionNodeKey, DefinitionNodeRef, Definitions, ExceptHandlerDefinitionNodeRef,
|
||||
ForStmtDefinitionKind, ForStmtDefinitionNodeRef, ImportDefinitionNodeRef,
|
||||
ImportFromDefinitionNodeRef, MatchPatternDefinitionNodeRef, StarImportDefinitionNodeRef,
|
||||
TargetKind, WithItemDefinitionKind, WithItemDefinitionNodeRef,
|
||||
AnnotatedAssignmentDefinitionNodeRef, AssignmentDefinitionNodeRef,
|
||||
ComprehensionDefinitionNodeRef, Definition, DefinitionCategory, DefinitionNodeKey,
|
||||
DefinitionNodeRef, Definitions, ExceptHandlerDefinitionNodeRef, ForStmtDefinitionNodeRef,
|
||||
ImportDefinitionNodeRef, ImportFromDefinitionNodeRef, MatchPatternDefinitionNodeRef,
|
||||
StarImportDefinitionNodeRef, WithItemDefinitionNodeRef,
|
||||
};
|
||||
use crate::semantic_index::expression::{Expression, ExpressionKind};
|
||||
use crate::semantic_index::place::{
|
||||
FileScopeId, NodeWithScopeKey, NodeWithScopeKind, NodeWithScopeRef, PlaceExpr,
|
||||
PlaceTableBuilder, Scope, ScopeId, ScopeKind, ScopedPlaceId,
|
||||
};
|
||||
use crate::semantic_index::predicate::{
|
||||
PatternPredicate, PatternPredicateKind, Predicate, PredicateNode, ScopedPredicateId,
|
||||
StarImportPlaceholderPredicate,
|
||||
};
|
||||
use crate::semantic_index::re_exports::exported_names;
|
||||
use crate::semantic_index::symbol::{
|
||||
FileScopeId, NodeWithScopeKey, NodeWithScopeKind, NodeWithScopeRef, Scope, ScopeId, ScopeKind,
|
||||
ScopedSymbolId, SymbolTableBuilder,
|
||||
};
|
||||
use crate::semantic_index::use_def::{
|
||||
EagerSnapshotKey, FlowSnapshot, ScopedEagerSnapshotId, UseDefMapBuilder,
|
||||
};
|
||||
|
@ -100,13 +98,12 @@ pub(super) struct SemanticIndexBuilder<'db> {
|
|||
// Semantic Index fields
|
||||
scopes: IndexVec<FileScopeId, Scope>,
|
||||
scope_ids_by_scope: IndexVec<FileScopeId, ScopeId<'db>>,
|
||||
symbol_tables: IndexVec<FileScopeId, SymbolTableBuilder>,
|
||||
instance_attribute_tables: IndexVec<FileScopeId, SymbolTableBuilder>,
|
||||
place_tables: IndexVec<FileScopeId, PlaceTableBuilder>,
|
||||
ast_ids: IndexVec<FileScopeId, AstIdsBuilder>,
|
||||
use_def_maps: IndexVec<FileScopeId, UseDefMapBuilder<'db>>,
|
||||
scopes_by_node: FxHashMap<NodeWithScopeKey, FileScopeId>,
|
||||
scopes_by_expression: FxHashMap<ExpressionNodeKey, FileScopeId>,
|
||||
globals_by_scope: FxHashMap<FileScopeId, FxHashSet<ScopedSymbolId>>,
|
||||
globals_by_scope: FxHashMap<FileScopeId, FxHashSet<ScopedPlaceId>>,
|
||||
definitions_by_node: FxHashMap<DefinitionNodeKey, Definitions<'db>>,
|
||||
expressions_by_node: FxHashMap<ExpressionNodeKey, Expression<'db>>,
|
||||
imported_modules: FxHashSet<ModuleName>,
|
||||
|
@ -135,8 +132,7 @@ impl<'db> SemanticIndexBuilder<'db> {
|
|||
has_future_annotations: false,
|
||||
|
||||
scopes: IndexVec::new(),
|
||||
symbol_tables: IndexVec::new(),
|
||||
instance_attribute_tables: IndexVec::new(),
|
||||
place_tables: IndexVec::new(),
|
||||
ast_ids: IndexVec::new(),
|
||||
scope_ids_by_scope: IndexVec::new(),
|
||||
use_def_maps: IndexVec::new(),
|
||||
|
@ -259,9 +255,7 @@ impl<'db> SemanticIndexBuilder<'db> {
|
|||
self.try_node_context_stack_manager.enter_nested_scope();
|
||||
|
||||
let file_scope_id = self.scopes.push(scope);
|
||||
self.symbol_tables.push(SymbolTableBuilder::default());
|
||||
self.instance_attribute_tables
|
||||
.push(SymbolTableBuilder::default());
|
||||
self.place_tables.push(PlaceTableBuilder::default());
|
||||
self.use_def_maps
|
||||
.push(UseDefMapBuilder::new(is_class_scope));
|
||||
let ast_id_scope = self.ast_ids.push(AstIdsBuilder::default());
|
||||
|
@ -301,36 +295,35 @@ impl<'db> SemanticIndexBuilder<'db> {
|
|||
|
||||
// If the scope that we just popped off is an eager scope, we need to "lock" our view of
|
||||
// which bindings reach each of the uses in the scope. Loop through each enclosing scope,
|
||||
// looking for any that bind each symbol.
|
||||
// looking for any that bind each place.
|
||||
for enclosing_scope_info in self.scope_stack.iter().rev() {
|
||||
let enclosing_scope_id = enclosing_scope_info.file_scope_id;
|
||||
let enclosing_scope_kind = self.scopes[enclosing_scope_id].kind();
|
||||
let enclosing_symbol_table = &self.symbol_tables[enclosing_scope_id];
|
||||
let enclosing_place_table = &self.place_tables[enclosing_scope_id];
|
||||
|
||||
for nested_symbol in self.symbol_tables[popped_scope_id].symbols() {
|
||||
// Skip this symbol if this enclosing scope doesn't contain any bindings for it.
|
||||
// Note that even if this symbol is bound in the popped scope,
|
||||
for nested_place in self.place_tables[popped_scope_id].places() {
|
||||
// Skip this place if this enclosing scope doesn't contain any bindings for it.
|
||||
// Note that even if this place is bound in the popped scope,
|
||||
// it may refer to the enclosing scope bindings
|
||||
// so we also need to snapshot the bindings of the enclosing scope.
|
||||
|
||||
let Some(enclosing_symbol_id) =
|
||||
enclosing_symbol_table.symbol_id_by_name(nested_symbol.name())
|
||||
let Some(enclosing_place_id) = enclosing_place_table.place_id_by_expr(nested_place)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let enclosing_symbol = enclosing_symbol_table.symbol(enclosing_symbol_id);
|
||||
let enclosing_place = enclosing_place_table.place_expr(enclosing_place_id);
|
||||
|
||||
// Snapshot the state of this symbol that are visible at this point in this
|
||||
// Snapshot the state of this place that are visible at this point in this
|
||||
// enclosing scope.
|
||||
let key = EagerSnapshotKey {
|
||||
enclosing_scope: enclosing_scope_id,
|
||||
enclosing_symbol: enclosing_symbol_id,
|
||||
enclosing_place: enclosing_place_id,
|
||||
nested_scope: popped_scope_id,
|
||||
};
|
||||
let eager_snapshot = self.use_def_maps[enclosing_scope_id].snapshot_eager_state(
|
||||
enclosing_symbol_id,
|
||||
enclosing_place_id,
|
||||
enclosing_scope_kind,
|
||||
enclosing_symbol.is_bound(),
|
||||
enclosing_place,
|
||||
);
|
||||
self.eager_snapshots.insert(key, eager_snapshot);
|
||||
}
|
||||
|
@ -338,7 +331,7 @@ impl<'db> SemanticIndexBuilder<'db> {
|
|||
// Lazy scopes are "sticky": once we see a lazy scope we stop doing lookups
|
||||
// eagerly, even if we would encounter another eager enclosing scope later on.
|
||||
// Also, narrowing constraints outside a lazy scope are not applicable.
|
||||
// TODO: If the symbol has never been rewritten, they are applicable.
|
||||
// TODO: If the place has never been rewritten, they are applicable.
|
||||
if !enclosing_scope_kind.is_eager() {
|
||||
break;
|
||||
}
|
||||
|
@ -347,14 +340,9 @@ impl<'db> SemanticIndexBuilder<'db> {
|
|||
popped_scope_id
|
||||
}
|
||||
|
||||
fn current_symbol_table(&mut self) -> &mut SymbolTableBuilder {
|
||||
fn current_place_table(&mut self) -> &mut PlaceTableBuilder {
|
||||
let scope_id = self.current_scope();
|
||||
&mut self.symbol_tables[scope_id]
|
||||
}
|
||||
|
||||
fn current_attribute_table(&mut self) -> &mut SymbolTableBuilder {
|
||||
let scope_id = self.current_scope();
|
||||
&mut self.instance_attribute_tables[scope_id]
|
||||
&mut self.place_tables[scope_id]
|
||||
}
|
||||
|
||||
fn current_use_def_map_mut(&mut self) -> &mut UseDefMapBuilder<'db> {
|
||||
|
@ -389,34 +377,36 @@ impl<'db> SemanticIndexBuilder<'db> {
|
|||
self.current_use_def_map_mut().merge(state);
|
||||
}
|
||||
|
||||
/// Add a symbol to the symbol table and the use-def map.
|
||||
/// Return the [`ScopedSymbolId`] that uniquely identifies the symbol in both.
|
||||
fn add_symbol(&mut self, name: Name) -> ScopedSymbolId {
|
||||
let (symbol_id, added) = self.current_symbol_table().add_symbol(name);
|
||||
/// Add a symbol to the place table and the use-def map.
|
||||
/// Return the [`ScopedPlaceId`] that uniquely identifies the symbol in both.
|
||||
fn add_symbol(&mut self, name: Name) -> ScopedPlaceId {
|
||||
let (place_id, added) = self.current_place_table().add_symbol(name);
|
||||
if added {
|
||||
self.current_use_def_map_mut().add_symbol(symbol_id);
|
||||
self.current_use_def_map_mut().add_place(place_id);
|
||||
}
|
||||
symbol_id
|
||||
place_id
|
||||
}
|
||||
|
||||
fn add_attribute(&mut self, name: Name) -> ScopedSymbolId {
|
||||
let (symbol_id, added) = self.current_attribute_table().add_symbol(name);
|
||||
/// Add a place to the place table and the use-def map.
|
||||
/// Return the [`ScopedPlaceId`] that uniquely identifies the place in both.
|
||||
fn add_place(&mut self, place_expr: PlaceExpr) -> ScopedPlaceId {
|
||||
let (place_id, added) = self.current_place_table().add_place(place_expr);
|
||||
if added {
|
||||
self.current_use_def_map_mut().add_attribute(symbol_id);
|
||||
self.current_use_def_map_mut().add_place(place_id);
|
||||
}
|
||||
symbol_id
|
||||
place_id
|
||||
}
|
||||
|
||||
fn mark_symbol_bound(&mut self, id: ScopedSymbolId) {
|
||||
self.current_symbol_table().mark_symbol_bound(id);
|
||||
fn mark_place_bound(&mut self, id: ScopedPlaceId) {
|
||||
self.current_place_table().mark_place_bound(id);
|
||||
}
|
||||
|
||||
fn mark_symbol_declared(&mut self, id: ScopedSymbolId) {
|
||||
self.current_symbol_table().mark_symbol_declared(id);
|
||||
fn mark_place_declared(&mut self, id: ScopedPlaceId) {
|
||||
self.current_place_table().mark_place_declared(id);
|
||||
}
|
||||
|
||||
fn mark_symbol_used(&mut self, id: ScopedSymbolId) {
|
||||
self.current_symbol_table().mark_symbol_used(id);
|
||||
fn mark_place_used(&mut self, id: ScopedPlaceId) {
|
||||
self.current_place_table().mark_place_used(id);
|
||||
}
|
||||
|
||||
fn add_entry_for_definition_key(&mut self, key: DefinitionNodeKey) -> &mut Definitions<'db> {
|
||||
|
@ -432,11 +422,10 @@ impl<'db> SemanticIndexBuilder<'db> {
|
|||
/// for all nodes *except* [`ast::Alias`] nodes representing `*` imports.
|
||||
fn add_definition(
|
||||
&mut self,
|
||||
symbol: ScopedSymbolId,
|
||||
place: ScopedPlaceId,
|
||||
definition_node: impl Into<DefinitionNodeRef<'db>> + std::fmt::Debug + Copy,
|
||||
) -> Definition<'db> {
|
||||
let (definition, num_definitions) =
|
||||
self.push_additional_definition(symbol, definition_node);
|
||||
let (definition, num_definitions) = self.push_additional_definition(place, definition_node);
|
||||
debug_assert_eq!(
|
||||
num_definitions, 1,
|
||||
"Attempted to create multiple `Definition`s associated with AST node {definition_node:?}"
|
||||
|
@ -444,6 +433,22 @@ impl<'db> SemanticIndexBuilder<'db> {
|
|||
definition
|
||||
}
|
||||
|
||||
fn delete_associated_bindings(&mut self, place: ScopedPlaceId) {
|
||||
let scope = self.current_scope();
|
||||
// Don't delete associated bindings if the scope is a class scope & place is a name (it's never visible to nested scopes)
|
||||
if self.scopes[scope].kind() == ScopeKind::Class
|
||||
&& self.place_tables[scope].place_expr(place).is_name()
|
||||
{
|
||||
return;
|
||||
}
|
||||
for associated_place in self.place_tables[scope].associated_place_ids(place) {
|
||||
let is_place_name = self.place_tables[scope]
|
||||
.place_expr(associated_place)
|
||||
.is_name();
|
||||
self.use_def_maps[scope].delete_binding(associated_place, is_place_name);
|
||||
}
|
||||
}
|
||||
|
||||
/// Push a new [`Definition`] onto the list of definitions
|
||||
/// associated with the `definition_node` AST node.
|
||||
///
|
||||
|
@ -457,7 +462,7 @@ impl<'db> SemanticIndexBuilder<'db> {
|
|||
/// prefer to use `self.add_definition()`, which ensures that this invariant is maintained.
|
||||
fn push_additional_definition(
|
||||
&mut self,
|
||||
symbol: ScopedSymbolId,
|
||||
place: ScopedPlaceId,
|
||||
definition_node: impl Into<DefinitionNodeRef<'db>>,
|
||||
) -> (Definition<'db>, usize) {
|
||||
let definition_node: DefinitionNodeRef<'_> = definition_node.into();
|
||||
|
@ -471,7 +476,7 @@ impl<'db> SemanticIndexBuilder<'db> {
|
|||
self.db,
|
||||
self.file,
|
||||
self.current_scope(),
|
||||
symbol,
|
||||
place,
|
||||
kind,
|
||||
is_reexported,
|
||||
countme::Count::default(),
|
||||
|
@ -484,19 +489,24 @@ impl<'db> SemanticIndexBuilder<'db> {
|
|||
};
|
||||
|
||||
if category.is_binding() {
|
||||
self.mark_symbol_bound(symbol);
|
||||
self.mark_place_bound(place);
|
||||
}
|
||||
if category.is_declaration() {
|
||||
self.mark_symbol_declared(symbol);
|
||||
self.mark_place_declared(place);
|
||||
}
|
||||
|
||||
let is_place_name = self.current_place_table().place_expr(place).is_name();
|
||||
let use_def = self.current_use_def_map_mut();
|
||||
match category {
|
||||
DefinitionCategory::DeclarationAndBinding => {
|
||||
use_def.record_declaration_and_binding(symbol, definition);
|
||||
use_def.record_declaration_and_binding(place, definition, is_place_name);
|
||||
self.delete_associated_bindings(place);
|
||||
}
|
||||
DefinitionCategory::Declaration => use_def.record_declaration(place, definition),
|
||||
DefinitionCategory::Binding => {
|
||||
use_def.record_binding(place, definition, is_place_name);
|
||||
self.delete_associated_bindings(place);
|
||||
}
|
||||
DefinitionCategory::Declaration => use_def.record_declaration(symbol, definition),
|
||||
DefinitionCategory::Binding => use_def.record_binding(symbol, definition),
|
||||
}
|
||||
|
||||
let mut try_node_stack_manager = std::mem::take(&mut self.try_node_context_stack_manager);
|
||||
|
@ -506,25 +516,6 @@ impl<'db> SemanticIndexBuilder<'db> {
|
|||
(definition, num_definitions)
|
||||
}
|
||||
|
||||
fn add_attribute_definition(
|
||||
&mut self,
|
||||
symbol: ScopedSymbolId,
|
||||
definition_kind: DefinitionKind<'db>,
|
||||
) -> Definition {
|
||||
let definition = Definition::new(
|
||||
self.db,
|
||||
self.file,
|
||||
self.current_scope(),
|
||||
symbol,
|
||||
definition_kind,
|
||||
false,
|
||||
countme::Count::default(),
|
||||
);
|
||||
self.current_use_def_map_mut()
|
||||
.record_attribute_binding(symbol, definition);
|
||||
definition
|
||||
}
|
||||
|
||||
fn record_expression_narrowing_constraint(
|
||||
&mut self,
|
||||
precide_node: &ast::Expr,
|
||||
|
@ -684,28 +675,6 @@ impl<'db> SemanticIndexBuilder<'db> {
|
|||
self.current_assignments.last_mut()
|
||||
}
|
||||
|
||||
/// Records the fact that we saw an attribute assignment of the form
|
||||
/// `object.attr: <annotation>( = …)` or `object.attr = <value>`.
|
||||
fn register_attribute_assignment(
|
||||
&mut self,
|
||||
object: &ast::Expr,
|
||||
attr: &'db ast::Identifier,
|
||||
definition_kind: DefinitionKind<'db>,
|
||||
) {
|
||||
if self.is_method_of_class().is_some() {
|
||||
// We only care about attribute assignments to the first parameter of a method,
|
||||
// i.e. typically `self` or `cls`.
|
||||
let accessed_object_refers_to_first_parameter =
|
||||
object.as_name_expr().map(|name| name.id.as_str())
|
||||
== self.current_first_parameter_name;
|
||||
|
||||
if accessed_object_refers_to_first_parameter {
|
||||
let symbol = self.add_attribute(attr.id().clone());
|
||||
self.add_attribute_definition(symbol, definition_kind);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn predicate_kind(&mut self, pattern: &ast::Pattern) -> PatternPredicateKind<'db> {
|
||||
match pattern {
|
||||
ast::Pattern::MatchValue(pattern) => {
|
||||
|
@ -850,8 +819,8 @@ impl<'db> SemanticIndexBuilder<'db> {
|
|||
// TODO create Definition for PEP 695 typevars
|
||||
// note that the "bound" on the typevar is a totally different thing than whether
|
||||
// or not a name is "bound" by a typevar declaration; the latter is always true.
|
||||
self.mark_symbol_bound(symbol);
|
||||
self.mark_symbol_declared(symbol);
|
||||
self.mark_place_bound(symbol);
|
||||
self.mark_place_declared(symbol);
|
||||
if let Some(bounds) = bound {
|
||||
self.visit_expr(bounds);
|
||||
}
|
||||
|
@ -1022,7 +991,7 @@ impl<'db> SemanticIndexBuilder<'db> {
|
|||
));
|
||||
Some(unpackable.as_current_assignment(unpack))
|
||||
}
|
||||
ast::Expr::Name(_) | ast::Expr::Attribute(_) => {
|
||||
ast::Expr::Name(_) | ast::Expr::Attribute(_) | ast::Expr::Subscript(_) => {
|
||||
Some(unpackable.as_current_assignment(None))
|
||||
}
|
||||
_ => None,
|
||||
|
@ -1050,18 +1019,12 @@ impl<'db> SemanticIndexBuilder<'db> {
|
|||
|
||||
assert_eq!(&self.current_assignments, &[]);
|
||||
|
||||
let mut symbol_tables: IndexVec<_, _> = self
|
||||
.symbol_tables
|
||||
let mut place_tables: IndexVec<_, _> = self
|
||||
.place_tables
|
||||
.into_iter()
|
||||
.map(|builder| Arc::new(builder.finish()))
|
||||
.collect();
|
||||
|
||||
let mut instance_attribute_tables: IndexVec<_, _> = self
|
||||
.instance_attribute_tables
|
||||
.into_iter()
|
||||
.map(SymbolTableBuilder::finish)
|
||||
.collect();
|
||||
|
||||
let mut use_def_maps: IndexVec<_, _> = self
|
||||
.use_def_maps
|
||||
.into_iter()
|
||||
|
@ -1075,8 +1038,7 @@ impl<'db> SemanticIndexBuilder<'db> {
|
|||
.collect();
|
||||
|
||||
self.scopes.shrink_to_fit();
|
||||
symbol_tables.shrink_to_fit();
|
||||
instance_attribute_tables.shrink_to_fit();
|
||||
place_tables.shrink_to_fit();
|
||||
use_def_maps.shrink_to_fit();
|
||||
ast_ids.shrink_to_fit();
|
||||
self.scopes_by_expression.shrink_to_fit();
|
||||
|
@ -1089,8 +1051,7 @@ impl<'db> SemanticIndexBuilder<'db> {
|
|||
self.globals_by_scope.shrink_to_fit();
|
||||
|
||||
SemanticIndex {
|
||||
symbol_tables,
|
||||
instance_attribute_tables,
|
||||
place_tables,
|
||||
scopes: self.scopes,
|
||||
definitions_by_node: self.definitions_by_node,
|
||||
expressions_by_node: self.expressions_by_node,
|
||||
|
@ -1213,7 +1174,7 @@ where
|
|||
// used to collect all the overloaded definitions of a function. This needs to be
|
||||
// done on the `Identifier` node as opposed to `ExprName` because that's what the
|
||||
// AST uses.
|
||||
self.mark_symbol_used(symbol);
|
||||
self.mark_place_used(symbol);
|
||||
let use_id = self.current_ast_ids().record_use(name);
|
||||
self.current_use_def_map_mut()
|
||||
.record_use(symbol, use_id, NodeKey::from_node(name));
|
||||
|
@ -1356,7 +1317,10 @@ where
|
|||
// For more details, see the doc-comment on `StarImportPlaceholderPredicate`.
|
||||
for export in exported_names(self.db, referenced_module) {
|
||||
let symbol_id = self.add_symbol(export.clone());
|
||||
let node_ref = StarImportDefinitionNodeRef { node, symbol_id };
|
||||
let node_ref = StarImportDefinitionNodeRef {
|
||||
node,
|
||||
place_id: symbol_id,
|
||||
};
|
||||
let star_import = StarImportPlaceholderPredicate::new(
|
||||
self.db,
|
||||
self.file,
|
||||
|
@ -1365,7 +1329,7 @@ where
|
|||
);
|
||||
|
||||
let pre_definition =
|
||||
self.current_use_def_map().single_symbol_snapshot(symbol_id);
|
||||
self.current_use_def_map().single_place_snapshot(symbol_id);
|
||||
self.push_additional_definition(symbol_id, node_ref);
|
||||
self.current_use_def_map_mut()
|
||||
.record_and_negate_star_import_visibility_constraint(
|
||||
|
@ -1920,8 +1884,8 @@ where
|
|||
ast::Stmt::Global(ast::StmtGlobal { range: _, names }) => {
|
||||
for name in names {
|
||||
let symbol_id = self.add_symbol(name.id.clone());
|
||||
let symbol_table = self.current_symbol_table();
|
||||
let symbol = symbol_table.symbol(symbol_id);
|
||||
let symbol_table = self.current_place_table();
|
||||
let symbol = symbol_table.place_expr(symbol_id);
|
||||
if symbol.is_bound() || symbol.is_declared() || symbol.is_used() {
|
||||
self.report_semantic_error(SemanticSyntaxError {
|
||||
kind: SemanticSyntaxErrorKind::LoadBeforeGlobalDeclaration {
|
||||
|
@ -1942,9 +1906,9 @@ where
|
|||
}
|
||||
ast::Stmt::Delete(ast::StmtDelete { targets, range: _ }) => {
|
||||
for target in targets {
|
||||
if let ast::Expr::Name(ast::ExprName { id, .. }) = target {
|
||||
let symbol_id = self.add_symbol(id.clone());
|
||||
self.current_symbol_table().mark_symbol_used(symbol_id);
|
||||
if let Ok(target) = PlaceExpr::try_from(target) {
|
||||
let place_id = self.add_place(target);
|
||||
self.current_place_table().mark_place_used(place_id);
|
||||
}
|
||||
}
|
||||
walk_stmt(self, stmt);
|
||||
|
@ -1971,109 +1935,133 @@ where
|
|||
let node_key = NodeKey::from_node(expr);
|
||||
|
||||
match expr {
|
||||
ast::Expr::Name(ast::ExprName { id, ctx, .. }) => {
|
||||
let (is_use, is_definition) = match (ctx, self.current_assignment()) {
|
||||
(ast::ExprContext::Store, Some(CurrentAssignment::AugAssign(_))) => {
|
||||
// For augmented assignment, the target expression is also used.
|
||||
(true, true)
|
||||
}
|
||||
(ast::ExprContext::Load, _) => (true, false),
|
||||
(ast::ExprContext::Store, _) => (false, true),
|
||||
(ast::ExprContext::Del, _) => (false, true),
|
||||
(ast::ExprContext::Invalid, _) => (false, false),
|
||||
};
|
||||
let symbol = self.add_symbol(id.clone());
|
||||
ast::Expr::Name(ast::ExprName { ctx, .. })
|
||||
| ast::Expr::Attribute(ast::ExprAttribute { ctx, .. })
|
||||
| ast::Expr::Subscript(ast::ExprSubscript { ctx, .. }) => {
|
||||
if let Ok(mut place_expr) = PlaceExpr::try_from(expr) {
|
||||
if self.is_method_of_class().is_some() {
|
||||
// We specifically mark attribute assignments to the first parameter of a method,
|
||||
// i.e. typically `self` or `cls`.
|
||||
let accessed_object_refers_to_first_parameter = self
|
||||
.current_first_parameter_name
|
||||
.is_some_and(|fst| place_expr.root_name().as_str() == fst);
|
||||
|
||||
if is_use {
|
||||
self.mark_symbol_used(symbol);
|
||||
let use_id = self.current_ast_ids().record_use(expr);
|
||||
if accessed_object_refers_to_first_parameter && place_expr.is_member() {
|
||||
place_expr.mark_instance_attribute();
|
||||
}
|
||||
}
|
||||
|
||||
let (is_use, is_definition) = match (ctx, self.current_assignment()) {
|
||||
(ast::ExprContext::Store, Some(CurrentAssignment::AugAssign(_))) => {
|
||||
// For augmented assignment, the target expression is also used.
|
||||
(true, true)
|
||||
}
|
||||
(ast::ExprContext::Load, _) => (true, false),
|
||||
(ast::ExprContext::Store, _) => (false, true),
|
||||
(ast::ExprContext::Del, _) => (false, true),
|
||||
(ast::ExprContext::Invalid, _) => (false, false),
|
||||
};
|
||||
let place_id = self.add_place(place_expr);
|
||||
|
||||
if is_use {
|
||||
self.mark_place_used(place_id);
|
||||
let use_id = self.current_ast_ids().record_use(expr);
|
||||
self.current_use_def_map_mut()
|
||||
.record_use(place_id, use_id, node_key);
|
||||
}
|
||||
|
||||
if is_definition {
|
||||
match self.current_assignment() {
|
||||
Some(CurrentAssignment::Assign { node, unpack }) => {
|
||||
self.add_definition(
|
||||
place_id,
|
||||
AssignmentDefinitionNodeRef {
|
||||
unpack,
|
||||
value: &node.value,
|
||||
target: expr,
|
||||
},
|
||||
);
|
||||
}
|
||||
Some(CurrentAssignment::AnnAssign(ann_assign)) => {
|
||||
self.add_standalone_type_expression(&ann_assign.annotation);
|
||||
self.add_definition(
|
||||
place_id,
|
||||
AnnotatedAssignmentDefinitionNodeRef {
|
||||
node: ann_assign,
|
||||
annotation: &ann_assign.annotation,
|
||||
value: ann_assign.value.as_deref(),
|
||||
target: expr,
|
||||
},
|
||||
);
|
||||
}
|
||||
Some(CurrentAssignment::AugAssign(aug_assign)) => {
|
||||
self.add_definition(place_id, aug_assign);
|
||||
}
|
||||
Some(CurrentAssignment::For { node, unpack }) => {
|
||||
self.add_definition(
|
||||
place_id,
|
||||
ForStmtDefinitionNodeRef {
|
||||
unpack,
|
||||
iterable: &node.iter,
|
||||
target: expr,
|
||||
is_async: node.is_async,
|
||||
},
|
||||
);
|
||||
}
|
||||
Some(CurrentAssignment::Named(named)) => {
|
||||
// TODO(dhruvmanila): If the current scope is a comprehension, then the
|
||||
// named expression is implicitly nonlocal. This is yet to be
|
||||
// implemented.
|
||||
self.add_definition(place_id, named);
|
||||
}
|
||||
Some(CurrentAssignment::Comprehension {
|
||||
unpack,
|
||||
node,
|
||||
first,
|
||||
}) => {
|
||||
self.add_definition(
|
||||
place_id,
|
||||
ComprehensionDefinitionNodeRef {
|
||||
unpack,
|
||||
iterable: &node.iter,
|
||||
target: expr,
|
||||
first,
|
||||
is_async: node.is_async,
|
||||
},
|
||||
);
|
||||
}
|
||||
Some(CurrentAssignment::WithItem {
|
||||
item,
|
||||
is_async,
|
||||
unpack,
|
||||
}) => {
|
||||
self.add_definition(
|
||||
place_id,
|
||||
WithItemDefinitionNodeRef {
|
||||
unpack,
|
||||
context_expr: &item.context_expr,
|
||||
target: expr,
|
||||
is_async,
|
||||
},
|
||||
);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(unpack_position) = self
|
||||
.current_assignment_mut()
|
||||
.and_then(CurrentAssignment::unpack_position_mut)
|
||||
{
|
||||
*unpack_position = UnpackPosition::Other;
|
||||
}
|
||||
}
|
||||
|
||||
// Track reachability of attribute expressions to silence `unresolved-attribute`
|
||||
// diagnostics in unreachable code.
|
||||
if expr.is_attribute_expr() {
|
||||
self.current_use_def_map_mut()
|
||||
.record_use(symbol, use_id, node_key);
|
||||
}
|
||||
|
||||
if is_definition {
|
||||
match self.current_assignment() {
|
||||
Some(CurrentAssignment::Assign { node, unpack }) => {
|
||||
self.add_definition(
|
||||
symbol,
|
||||
AssignmentDefinitionNodeRef {
|
||||
unpack,
|
||||
value: &node.value,
|
||||
target: expr,
|
||||
},
|
||||
);
|
||||
}
|
||||
Some(CurrentAssignment::AnnAssign(ann_assign)) => {
|
||||
self.add_definition(
|
||||
symbol,
|
||||
AnnotatedAssignmentDefinitionNodeRef {
|
||||
node: ann_assign,
|
||||
annotation: &ann_assign.annotation,
|
||||
value: ann_assign.value.as_deref(),
|
||||
target: expr,
|
||||
},
|
||||
);
|
||||
}
|
||||
Some(CurrentAssignment::AugAssign(aug_assign)) => {
|
||||
self.add_definition(symbol, aug_assign);
|
||||
}
|
||||
Some(CurrentAssignment::For { node, unpack }) => {
|
||||
self.add_definition(
|
||||
symbol,
|
||||
ForStmtDefinitionNodeRef {
|
||||
unpack,
|
||||
iterable: &node.iter,
|
||||
target: expr,
|
||||
is_async: node.is_async,
|
||||
},
|
||||
);
|
||||
}
|
||||
Some(CurrentAssignment::Named(named)) => {
|
||||
// TODO(dhruvmanila): If the current scope is a comprehension, then the
|
||||
// named expression is implicitly nonlocal. This is yet to be
|
||||
// implemented.
|
||||
self.add_definition(symbol, named);
|
||||
}
|
||||
Some(CurrentAssignment::Comprehension {
|
||||
unpack,
|
||||
node,
|
||||
first,
|
||||
}) => {
|
||||
self.add_definition(
|
||||
symbol,
|
||||
ComprehensionDefinitionNodeRef {
|
||||
unpack,
|
||||
iterable: &node.iter,
|
||||
target: expr,
|
||||
first,
|
||||
is_async: node.is_async,
|
||||
},
|
||||
);
|
||||
}
|
||||
Some(CurrentAssignment::WithItem {
|
||||
item,
|
||||
is_async,
|
||||
unpack,
|
||||
}) => {
|
||||
self.add_definition(
|
||||
symbol,
|
||||
WithItemDefinitionNodeRef {
|
||||
unpack,
|
||||
context_expr: &item.context_expr,
|
||||
target: expr,
|
||||
is_async,
|
||||
},
|
||||
);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(unpack_position) = self
|
||||
.current_assignment_mut()
|
||||
.and_then(CurrentAssignment::unpack_position_mut)
|
||||
{
|
||||
*unpack_position = UnpackPosition::Other;
|
||||
.record_node_reachability(node_key);
|
||||
}
|
||||
|
||||
walk_expr(self, expr);
|
||||
|
@ -2239,125 +2227,6 @@ where
|
|||
|
||||
self.simplify_visibility_constraints(pre_op);
|
||||
}
|
||||
ast::Expr::Attribute(ast::ExprAttribute {
|
||||
value: object,
|
||||
attr,
|
||||
ctx,
|
||||
range: _,
|
||||
}) => {
|
||||
if ctx.is_store() {
|
||||
match self.current_assignment() {
|
||||
Some(CurrentAssignment::Assign { node, unpack, .. }) => {
|
||||
// SAFETY: `value` and `expr` belong to the `self.module` tree
|
||||
#[expect(unsafe_code)]
|
||||
let assignment = AssignmentDefinitionKind::new(
|
||||
TargetKind::from(unpack),
|
||||
unsafe { AstNodeRef::new(self.module.clone(), &node.value) },
|
||||
unsafe { AstNodeRef::new(self.module.clone(), expr) },
|
||||
);
|
||||
self.register_attribute_assignment(
|
||||
object,
|
||||
attr,
|
||||
DefinitionKind::Assignment(assignment),
|
||||
);
|
||||
}
|
||||
Some(CurrentAssignment::AnnAssign(ann_assign)) => {
|
||||
self.add_standalone_type_expression(&ann_assign.annotation);
|
||||
// SAFETY: `annotation`, `value` and `expr` belong to the `self.module` tree
|
||||
#[expect(unsafe_code)]
|
||||
let assignment = AnnotatedAssignmentDefinitionKind::new(
|
||||
unsafe {
|
||||
AstNodeRef::new(self.module.clone(), &ann_assign.annotation)
|
||||
},
|
||||
ann_assign.value.as_deref().map(|value| unsafe {
|
||||
AstNodeRef::new(self.module.clone(), value)
|
||||
}),
|
||||
unsafe { AstNodeRef::new(self.module.clone(), expr) },
|
||||
);
|
||||
self.register_attribute_assignment(
|
||||
object,
|
||||
attr,
|
||||
DefinitionKind::AnnotatedAssignment(assignment),
|
||||
);
|
||||
}
|
||||
Some(CurrentAssignment::For { node, unpack, .. }) => {
|
||||
// // SAFETY: `iter` and `expr` belong to the `self.module` tree
|
||||
#[expect(unsafe_code)]
|
||||
let assignment = ForStmtDefinitionKind::new(
|
||||
TargetKind::from(unpack),
|
||||
unsafe { AstNodeRef::new(self.module.clone(), &node.iter) },
|
||||
unsafe { AstNodeRef::new(self.module.clone(), expr) },
|
||||
node.is_async,
|
||||
);
|
||||
self.register_attribute_assignment(
|
||||
object,
|
||||
attr,
|
||||
DefinitionKind::For(assignment),
|
||||
);
|
||||
}
|
||||
Some(CurrentAssignment::WithItem {
|
||||
item,
|
||||
unpack,
|
||||
is_async,
|
||||
..
|
||||
}) => {
|
||||
// SAFETY: `context_expr` and `expr` belong to the `self.module` tree
|
||||
#[expect(unsafe_code)]
|
||||
let assignment = WithItemDefinitionKind::new(
|
||||
TargetKind::from(unpack),
|
||||
unsafe { AstNodeRef::new(self.module.clone(), &item.context_expr) },
|
||||
unsafe { AstNodeRef::new(self.module.clone(), expr) },
|
||||
is_async,
|
||||
);
|
||||
self.register_attribute_assignment(
|
||||
object,
|
||||
attr,
|
||||
DefinitionKind::WithItem(assignment),
|
||||
);
|
||||
}
|
||||
Some(CurrentAssignment::Comprehension {
|
||||
unpack,
|
||||
node,
|
||||
first,
|
||||
}) => {
|
||||
// SAFETY: `iter` and `expr` belong to the `self.module` tree
|
||||
#[expect(unsafe_code)]
|
||||
let assignment = ComprehensionDefinitionKind {
|
||||
target_kind: TargetKind::from(unpack),
|
||||
iterable: unsafe {
|
||||
AstNodeRef::new(self.module.clone(), &node.iter)
|
||||
},
|
||||
target: unsafe { AstNodeRef::new(self.module.clone(), expr) },
|
||||
first,
|
||||
is_async: node.is_async,
|
||||
};
|
||||
// Temporarily move to the scope of the method to which the instance attribute is defined.
|
||||
// SAFETY: `self.scope_stack` is not empty because the targets in comprehensions should always introduce a new scope.
|
||||
let scope = self.scope_stack.pop().expect("The popped scope must be a comprehension, which must have a parent scope");
|
||||
self.register_attribute_assignment(
|
||||
object,
|
||||
attr,
|
||||
DefinitionKind::Comprehension(assignment),
|
||||
);
|
||||
self.scope_stack.push(scope);
|
||||
}
|
||||
Some(CurrentAssignment::AugAssign(_)) => {
|
||||
// TODO:
|
||||
}
|
||||
Some(CurrentAssignment::Named(_)) => {
|
||||
// A named expression whose target is an attribute is syntactically prohibited
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Track reachability of attribute expressions to silence `unresolved-attribute`
|
||||
// diagnostics in unreachable code.
|
||||
self.current_use_def_map_mut()
|
||||
.record_node_reachability(node_key);
|
||||
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
ast::Expr::StringLiteral(_) => {
|
||||
// Track reachability of string literals, as they could be a stringified annotation
|
||||
// with child expressions whose reachability we are interested in.
|
||||
|
|
|
@ -8,16 +8,16 @@ use ruff_text_size::{Ranged, TextRange};
|
|||
use crate::Db;
|
||||
use crate::ast_node_ref::AstNodeRef;
|
||||
use crate::node_key::NodeKey;
|
||||
use crate::semantic_index::symbol::{FileScopeId, ScopeId, ScopedSymbolId};
|
||||
use crate::semantic_index::place::{FileScopeId, ScopeId, ScopedPlaceId};
|
||||
use crate::unpack::{Unpack, UnpackPosition};
|
||||
|
||||
/// A definition of a symbol.
|
||||
/// A definition of a place.
|
||||
///
|
||||
/// ## ID stability
|
||||
/// The `Definition`'s ID is stable when the only field that change is its `kind` (AST node).
|
||||
///
|
||||
/// The `Definition` changes when the `file`, `scope`, or `symbol` change. This can be
|
||||
/// because a new scope gets inserted before the `Definition` or a new symbol is inserted
|
||||
/// The `Definition` changes when the `file`, `scope`, or `place` change. This can be
|
||||
/// because a new scope gets inserted before the `Definition` or a new place is inserted
|
||||
/// before this `Definition`. However, the ID can be considered stable and it is okay to use
|
||||
/// `Definition` in cross-module` salsa queries or as a field on other salsa tracked structs.
|
||||
#[salsa::tracked(debug)]
|
||||
|
@ -28,8 +28,8 @@ pub struct Definition<'db> {
|
|||
/// The scope in which the definition occurs.
|
||||
pub(crate) file_scope: FileScopeId,
|
||||
|
||||
/// The symbol defined.
|
||||
pub(crate) symbol: ScopedSymbolId,
|
||||
/// The place ID of the definition.
|
||||
pub(crate) place: ScopedPlaceId,
|
||||
|
||||
/// WARNING: Only access this field when doing type inference for the same
|
||||
/// file as where `Definition` is defined to avoid cross-file query dependencies.
|
||||
|
@ -89,6 +89,39 @@ impl<'a, 'db> IntoIterator for &'a Definitions<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, salsa::Update)]
|
||||
pub(crate) enum DefinitionState<'db> {
|
||||
Defined(Definition<'db>),
|
||||
/// Represents the implicit "unbound"/"undeclared" definition of every place.
|
||||
Undefined,
|
||||
/// Represents a definition that has been deleted.
|
||||
/// This used when an attribute/subscript definition (such as `x.y = ...`, `x[0] = ...`) becomes obsolete due to a reassignment of the root place.
|
||||
Deleted,
|
||||
}
|
||||
|
||||
impl<'db> DefinitionState<'db> {
|
||||
pub(crate) fn is_defined_and(self, f: impl Fn(Definition<'db>) -> bool) -> bool {
|
||||
matches!(self, DefinitionState::Defined(def) if f(def))
|
||||
}
|
||||
|
||||
pub(crate) fn is_undefined_or(self, f: impl Fn(Definition<'db>) -> bool) -> bool {
|
||||
matches!(self, DefinitionState::Undefined)
|
||||
|| matches!(self, DefinitionState::Defined(def) if f(def))
|
||||
}
|
||||
|
||||
pub(crate) fn is_undefined(self) -> bool {
|
||||
matches!(self, DefinitionState::Undefined)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn definition(self) -> Option<Definition<'db>> {
|
||||
match self {
|
||||
DefinitionState::Defined(def) => Some(def),
|
||||
DefinitionState::Deleted | DefinitionState::Undefined => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) enum DefinitionNodeRef<'a> {
|
||||
Import(ImportDefinitionNodeRef<'a>),
|
||||
|
@ -232,7 +265,7 @@ pub(crate) struct ImportDefinitionNodeRef<'a> {
|
|||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) struct StarImportDefinitionNodeRef<'a> {
|
||||
pub(crate) node: &'a ast::StmtImportFrom,
|
||||
pub(crate) symbol_id: ScopedSymbolId,
|
||||
pub(crate) place_id: ScopedPlaceId,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
|
@ -323,10 +356,10 @@ impl<'db> DefinitionNodeRef<'db> {
|
|||
is_reexported,
|
||||
}),
|
||||
DefinitionNodeRef::ImportStar(star_import) => {
|
||||
let StarImportDefinitionNodeRef { node, symbol_id } = star_import;
|
||||
let StarImportDefinitionNodeRef { node, place_id } = star_import;
|
||||
DefinitionKind::StarImport(StarImportDefinitionKind {
|
||||
node: unsafe { AstNodeRef::new(parsed, node) },
|
||||
symbol_id,
|
||||
place_id,
|
||||
})
|
||||
}
|
||||
DefinitionNodeRef::Function(function) => {
|
||||
|
@ -456,7 +489,7 @@ impl<'db> DefinitionNodeRef<'db> {
|
|||
|
||||
// INVARIANT: for an invalid-syntax statement such as `from foo import *, bar, *`,
|
||||
// we only create a `StarImportDefinitionKind` for the *first* `*` alias in the names list.
|
||||
Self::ImportStar(StarImportDefinitionNodeRef { node, symbol_id: _ }) => node
|
||||
Self::ImportStar(StarImportDefinitionNodeRef { node, place_id: _ }) => node
|
||||
.names
|
||||
.iter()
|
||||
.find(|alias| &alias.name == "*")
|
||||
|
@ -517,7 +550,7 @@ pub(crate) enum DefinitionCategory {
|
|||
}
|
||||
|
||||
impl DefinitionCategory {
|
||||
/// True if this definition establishes a "declared type" for the symbol.
|
||||
/// True if this definition establishes a "declared type" for the place.
|
||||
///
|
||||
/// If so, any assignments reached by this definition are in error if they assign a value of a
|
||||
/// type not assignable to the declared type.
|
||||
|
@ -530,7 +563,7 @@ impl DefinitionCategory {
|
|||
)
|
||||
}
|
||||
|
||||
/// True if this definition assigns a value to the symbol.
|
||||
/// True if this definition assigns a value to the place.
|
||||
///
|
||||
/// False only for annotated assignments without a RHS.
|
||||
pub(crate) fn is_binding(self) -> bool {
|
||||
|
@ -591,8 +624,8 @@ impl DefinitionKind<'_> {
|
|||
|
||||
/// Returns the [`TextRange`] of the definition target.
|
||||
///
|
||||
/// A definition target would mainly be the node representing the symbol being defined i.e.,
|
||||
/// [`ast::ExprName`] or [`ast::Identifier`] but could also be other nodes.
|
||||
/// A definition target would mainly be the node representing the place being defined i.e.,
|
||||
/// [`ast::ExprName`], [`ast::Identifier`], [`ast::ExprAttribute`] or [`ast::ExprSubscript`] but could also be other nodes.
|
||||
pub(crate) fn target_range(&self) -> TextRange {
|
||||
match self {
|
||||
DefinitionKind::Import(import) => import.alias().range(),
|
||||
|
@ -700,14 +733,15 @@ impl DefinitionKind<'_> {
|
|||
#[derive(Copy, Clone, Debug, PartialEq, Hash)]
|
||||
pub(crate) enum TargetKind<'db> {
|
||||
Sequence(UnpackPosition, Unpack<'db>),
|
||||
NameOrAttribute,
|
||||
/// Name, attribute, or subscript.
|
||||
Single,
|
||||
}
|
||||
|
||||
impl<'db> From<Option<(UnpackPosition, Unpack<'db>)>> for TargetKind<'db> {
|
||||
fn from(value: Option<(UnpackPosition, Unpack<'db>)>) -> Self {
|
||||
match value {
|
||||
Some((unpack_position, unpack)) => TargetKind::Sequence(unpack_position, unpack),
|
||||
None => TargetKind::NameOrAttribute,
|
||||
None => TargetKind::Single,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -715,7 +749,7 @@ impl<'db> From<Option<(UnpackPosition, Unpack<'db>)>> for TargetKind<'db> {
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct StarImportDefinitionKind {
|
||||
node: AstNodeRef<ast::StmtImportFrom>,
|
||||
symbol_id: ScopedSymbolId,
|
||||
place_id: ScopedPlaceId,
|
||||
}
|
||||
|
||||
impl StarImportDefinitionKind {
|
||||
|
@ -737,8 +771,8 @@ impl StarImportDefinitionKind {
|
|||
)
|
||||
}
|
||||
|
||||
pub(crate) fn symbol_id(&self) -> ScopedSymbolId {
|
||||
self.symbol_id
|
||||
pub(crate) fn place_id(&self) -> ScopedPlaceId {
|
||||
self.place_id
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -759,13 +793,18 @@ impl MatchPatternDefinitionKind {
|
|||
}
|
||||
}
|
||||
|
||||
/// Note that the elements of a comprehension can be in different scopes.
|
||||
/// If the definition target of a comprehension is a name, it is in the comprehension's scope.
|
||||
/// But if the target is an attribute or subscript, its definition is not in the comprehension's scope;
|
||||
/// it is in the scope in which the root variable is bound.
|
||||
/// TODO: currently we don't model this correctly and simply assume that it is in a scope outside the comprehension.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ComprehensionDefinitionKind<'db> {
|
||||
pub(super) target_kind: TargetKind<'db>,
|
||||
pub(super) iterable: AstNodeRef<ast::Expr>,
|
||||
pub(super) target: AstNodeRef<ast::Expr>,
|
||||
pub(super) first: bool,
|
||||
pub(super) is_async: bool,
|
||||
target_kind: TargetKind<'db>,
|
||||
iterable: AstNodeRef<ast::Expr>,
|
||||
target: AstNodeRef<ast::Expr>,
|
||||
first: bool,
|
||||
is_async: bool,
|
||||
}
|
||||
|
||||
impl<'db> ComprehensionDefinitionKind<'db> {
|
||||
|
@ -840,18 +879,6 @@ pub struct AssignmentDefinitionKind<'db> {
|
|||
}
|
||||
|
||||
impl<'db> AssignmentDefinitionKind<'db> {
|
||||
pub(crate) fn new(
|
||||
target_kind: TargetKind<'db>,
|
||||
value: AstNodeRef<ast::Expr>,
|
||||
target: AstNodeRef<ast::Expr>,
|
||||
) -> Self {
|
||||
Self {
|
||||
target_kind,
|
||||
value,
|
||||
target,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn target_kind(&self) -> TargetKind<'db> {
|
||||
self.target_kind
|
||||
}
|
||||
|
@ -873,18 +900,6 @@ pub struct AnnotatedAssignmentDefinitionKind {
|
|||
}
|
||||
|
||||
impl AnnotatedAssignmentDefinitionKind {
|
||||
pub(crate) fn new(
|
||||
annotation: AstNodeRef<ast::Expr>,
|
||||
value: Option<AstNodeRef<ast::Expr>>,
|
||||
target: AstNodeRef<ast::Expr>,
|
||||
) -> Self {
|
||||
Self {
|
||||
annotation,
|
||||
value,
|
||||
target,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn value(&self) -> Option<&ast::Expr> {
|
||||
self.value.as_deref()
|
||||
}
|
||||
|
@ -907,20 +922,6 @@ pub struct WithItemDefinitionKind<'db> {
|
|||
}
|
||||
|
||||
impl<'db> WithItemDefinitionKind<'db> {
|
||||
pub(crate) fn new(
|
||||
target_kind: TargetKind<'db>,
|
||||
context_expr: AstNodeRef<ast::Expr>,
|
||||
target: AstNodeRef<ast::Expr>,
|
||||
is_async: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
target_kind,
|
||||
context_expr,
|
||||
target,
|
||||
is_async,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn context_expr(&self) -> &ast::Expr {
|
||||
self.context_expr.node()
|
||||
}
|
||||
|
@ -947,20 +948,6 @@ pub struct ForStmtDefinitionKind<'db> {
|
|||
}
|
||||
|
||||
impl<'db> ForStmtDefinitionKind<'db> {
|
||||
pub(crate) fn new(
|
||||
target_kind: TargetKind<'db>,
|
||||
iterable: AstNodeRef<ast::Expr>,
|
||||
target: AstNodeRef<ast::Expr>,
|
||||
is_async: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
target_kind,
|
||||
iterable,
|
||||
target,
|
||||
is_async,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn iterable(&self) -> &ast::Expr {
|
||||
self.iterable.node()
|
||||
}
|
||||
|
@ -1031,6 +1018,18 @@ impl From<&ast::ExprName> for DefinitionNodeKey {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<&ast::ExprAttribute> for DefinitionNodeKey {
|
||||
fn from(node: &ast::ExprAttribute) -> Self {
|
||||
Self(NodeKey::from_node(node))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ast::ExprSubscript> for DefinitionNodeKey {
|
||||
fn from(node: &ast::ExprSubscript) -> Self {
|
||||
Self(NodeKey::from_node(node))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ast::ExprNamed> for DefinitionNodeKey {
|
||||
fn from(node: &ast::ExprNamed) -> Self {
|
||||
Self(NodeKey::from_node(node))
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::ast_node_ref::AstNodeRef;
|
||||
use crate::db::Db;
|
||||
use crate::semantic_index::symbol::{FileScopeId, ScopeId};
|
||||
use crate::semantic_index::place::{FileScopeId, ScopeId};
|
||||
use ruff_db::files::File;
|
||||
use ruff_python_ast as ast;
|
||||
use salsa;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! # Narrowing constraints
|
||||
//!
|
||||
//! When building a semantic index for a file, we associate each binding with a _narrowing
|
||||
//! constraint_, which constrains the type of the binding's symbol. Note that a binding can be
|
||||
//! constraint_, which constrains the type of the binding's place. Note that a binding can be
|
||||
//! associated with a different narrowing constraint at different points in a file. See the
|
||||
//! [`use_def`][crate::semantic_index::use_def] module for more details.
|
||||
//!
|
||||
|
@ -34,7 +34,7 @@ use crate::semantic_index::predicate::ScopedPredicateId;
|
|||
|
||||
/// A narrowing constraint associated with a live binding.
|
||||
///
|
||||
/// A constraint is a list of [`Predicate`]s that each constrain the type of the binding's symbol.
|
||||
/// A constraint is a list of [`Predicate`]s that each constrain the type of the binding's place.
|
||||
///
|
||||
/// [`Predicate`]: crate::semantic_index::predicate::Predicate
|
||||
pub(crate) type ScopedNarrowingConstraint = List<ScopedNarrowingConstraintPredicate>;
|
||||
|
@ -46,7 +46,7 @@ pub(crate) enum ConstraintKey {
|
|||
}
|
||||
|
||||
/// One of the [`Predicate`]s in a narrowing constraint, which constraints the type of the
|
||||
/// binding's symbol.
|
||||
/// binding's place.
|
||||
///
|
||||
/// Note that those [`Predicate`]s are stored in [their own per-scope
|
||||
/// arena][crate::semantic_index::predicate::Predicates], so internally we use a
|
||||
|
|
942
crates/ty_python_semantic/src/semantic_index/place.rs
Normal file
942
crates/ty_python_semantic/src/semantic_index/place.rs
Normal file
|
@ -0,0 +1,942 @@
|
|||
use std::convert::Infallible;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::Range;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use hashbrown::hash_map::RawEntryMut;
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::parsed::ParsedModule;
|
||||
use ruff_index::{IndexVec, newtype_index};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::name::Name;
|
||||
use rustc_hash::FxHasher;
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
|
||||
use crate::Db;
|
||||
use crate::ast_node_ref::AstNodeRef;
|
||||
use crate::node_key::NodeKey;
|
||||
use crate::semantic_index::visibility_constraints::ScopedVisibilityConstraintId;
|
||||
use crate::semantic_index::{PlaceSet, SemanticIndex, semantic_index};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, salsa::Update)]
|
||||
pub(crate) enum PlaceExprSubSegment {
|
||||
/// A member access, e.g. `.y` in `x.y`
|
||||
Member(ast::name::Name),
|
||||
/// An integer-based index access, e.g. `[1]` in `x[1]`
|
||||
IntSubscript(ast::Int),
|
||||
/// A string-based index access, e.g. `["foo"]` in `x["foo"]`
|
||||
StringSubscript(String),
|
||||
}
|
||||
|
||||
impl PlaceExprSubSegment {
|
||||
pub(crate) fn as_member(&self) -> Option<&ast::name::Name> {
|
||||
match self {
|
||||
PlaceExprSubSegment::Member(name) => Some(name),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An expression that can be the target of a `Definition`.
|
||||
/// If you want to perform a comparison based on the equality of segments (without including
|
||||
/// flags), use [`PlaceSegments`].
|
||||
#[derive(Eq, PartialEq, Debug)]
|
||||
pub struct PlaceExpr {
|
||||
root_name: Name,
|
||||
sub_segments: SmallVec<[PlaceExprSubSegment; 1]>,
|
||||
flags: PlaceFlags,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for PlaceExpr {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.root_name)?;
|
||||
for segment in &self.sub_segments {
|
||||
match segment {
|
||||
PlaceExprSubSegment::Member(name) => write!(f, ".{name}")?,
|
||||
PlaceExprSubSegment::IntSubscript(int) => write!(f, "[{int}]")?,
|
||||
PlaceExprSubSegment::StringSubscript(string) => write!(f, "[\"{string}\"]")?,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&ast::name::Name> for PlaceExpr {
|
||||
type Error = Infallible;
|
||||
|
||||
fn try_from(name: &ast::name::Name) -> Result<Self, Infallible> {
|
||||
Ok(PlaceExpr::name(name.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ast::name::Name> for PlaceExpr {
|
||||
type Error = Infallible;
|
||||
|
||||
fn try_from(name: ast::name::Name) -> Result<Self, Infallible> {
|
||||
Ok(PlaceExpr::name(name))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&ast::ExprAttribute> for PlaceExpr {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(attr: &ast::ExprAttribute) -> Result<Self, ()> {
|
||||
let mut place = PlaceExpr::try_from(&*attr.value)?;
|
||||
place
|
||||
.sub_segments
|
||||
.push(PlaceExprSubSegment::Member(attr.attr.id.clone()));
|
||||
Ok(place)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ast::ExprAttribute> for PlaceExpr {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(attr: ast::ExprAttribute) -> Result<Self, ()> {
|
||||
let mut place = PlaceExpr::try_from(&*attr.value)?;
|
||||
place
|
||||
.sub_segments
|
||||
.push(PlaceExprSubSegment::Member(attr.attr.id));
|
||||
Ok(place)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&ast::ExprSubscript> for PlaceExpr {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(subscript: &ast::ExprSubscript) -> Result<Self, ()> {
|
||||
let mut place = PlaceExpr::try_from(&*subscript.value)?;
|
||||
match &*subscript.slice {
|
||||
ast::Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
value: ast::Number::Int(index),
|
||||
..
|
||||
}) => {
|
||||
place
|
||||
.sub_segments
|
||||
.push(PlaceExprSubSegment::IntSubscript(index.clone()));
|
||||
}
|
||||
ast::Expr::StringLiteral(string) => {
|
||||
place
|
||||
.sub_segments
|
||||
.push(PlaceExprSubSegment::StringSubscript(
|
||||
string.value.to_string(),
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
Ok(place)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ast::ExprSubscript> for PlaceExpr {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(subscript: ast::ExprSubscript) -> Result<Self, ()> {
|
||||
PlaceExpr::try_from(&subscript)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&ast::Expr> for PlaceExpr {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(expr: &ast::Expr) -> Result<Self, ()> {
|
||||
match expr {
|
||||
ast::Expr::Name(name) => Ok(PlaceExpr::name(name.id.clone())),
|
||||
ast::Expr::Attribute(attr) => PlaceExpr::try_from(attr),
|
||||
ast::Expr::Subscript(subscript) => PlaceExpr::try_from(subscript),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PlaceExpr {
|
||||
pub(super) fn name(name: Name) -> Self {
|
||||
Self {
|
||||
root_name: name,
|
||||
sub_segments: smallvec![],
|
||||
flags: PlaceFlags::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_flags(&mut self, flags: PlaceFlags) {
|
||||
self.flags.insert(flags);
|
||||
}
|
||||
|
||||
pub(super) fn mark_instance_attribute(&mut self) {
|
||||
self.flags.insert(PlaceFlags::IS_INSTANCE_ATTRIBUTE);
|
||||
}
|
||||
|
||||
pub(crate) fn root_name(&self) -> &Name {
|
||||
&self.root_name
|
||||
}
|
||||
|
||||
pub(crate) fn sub_segments(&self) -> &[PlaceExprSubSegment] {
|
||||
&self.sub_segments
|
||||
}
|
||||
|
||||
pub(crate) fn as_name(&self) -> Option<&Name> {
|
||||
if self.is_name() {
|
||||
Some(&self.root_name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Assumes that the place expression is a name.
|
||||
#[track_caller]
|
||||
pub(crate) fn expect_name(&self) -> &Name {
|
||||
debug_assert_eq!(self.sub_segments.len(), 0);
|
||||
&self.root_name
|
||||
}
|
||||
|
||||
/// Does the place expression have the form `self.{name}` (`self` is the first parameter of the method)?
|
||||
pub(super) fn is_instance_attribute_named(&self, name: &str) -> bool {
|
||||
self.is_instance_attribute()
|
||||
&& self.sub_segments.len() == 1
|
||||
&& self.sub_segments[0].as_member().unwrap().as_str() == name
|
||||
}
|
||||
|
||||
/// Is the place an instance attribute?
|
||||
pub fn is_instance_attribute(&self) -> bool {
|
||||
self.flags.contains(PlaceFlags::IS_INSTANCE_ATTRIBUTE)
|
||||
}
|
||||
|
||||
/// Is the place used in its containing scope?
|
||||
pub fn is_used(&self) -> bool {
|
||||
self.flags.contains(PlaceFlags::IS_USED)
|
||||
}
|
||||
|
||||
/// Is the place defined in its containing scope?
|
||||
pub fn is_bound(&self) -> bool {
|
||||
self.flags.contains(PlaceFlags::IS_BOUND)
|
||||
}
|
||||
|
||||
/// Is the place declared in its containing scope?
|
||||
pub fn is_declared(&self) -> bool {
|
||||
self.flags.contains(PlaceFlags::IS_DECLARED)
|
||||
}
|
||||
|
||||
/// Is the place just a name?
|
||||
pub fn is_name(&self) -> bool {
|
||||
self.sub_segments.is_empty()
|
||||
}
|
||||
|
||||
pub fn is_name_and(&self, f: impl FnOnce(&str) -> bool) -> bool {
|
||||
self.is_name() && f(&self.root_name)
|
||||
}
|
||||
|
||||
/// Does the place expression have the form `<object>.member`?
|
||||
pub fn is_member(&self) -> bool {
|
||||
self.sub_segments
|
||||
.last()
|
||||
.is_some_and(|last| last.as_member().is_some())
|
||||
}
|
||||
|
||||
pub(crate) fn segments(&self) -> PlaceSegments {
|
||||
PlaceSegments {
|
||||
root_name: Some(&self.root_name),
|
||||
sub_segments: &self.sub_segments,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Ideally this would iterate PlaceSegments instead of RootExprs, both to reduce
|
||||
// allocation and to avoid having both flagged and non-flagged versions of PlaceExprs.
|
||||
fn root_exprs(&self) -> RootExprs<'_> {
|
||||
RootExprs {
|
||||
expr: self,
|
||||
len: self.sub_segments.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RootExprs<'e> {
|
||||
expr: &'e PlaceExpr,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl Iterator for RootExprs<'_> {
|
||||
type Item = PlaceExpr;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.len == 0 {
|
||||
return None;
|
||||
}
|
||||
self.len -= 1;
|
||||
Some(PlaceExpr {
|
||||
root_name: self.expr.root_name.clone(),
|
||||
sub_segments: self.expr.sub_segments[..self.len].iter().cloned().collect(),
|
||||
flags: PlaceFlags::empty(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Flags that can be queried to obtain information about a place in a given scope.
|
||||
///
|
||||
/// See the doc-comment at the top of [`super::use_def`] for explanations of what it
|
||||
/// means for a place to be *bound* as opposed to *declared*.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
struct PlaceFlags: u8 {
|
||||
const IS_USED = 1 << 0;
|
||||
const IS_BOUND = 1 << 1;
|
||||
const IS_DECLARED = 1 << 2;
|
||||
/// TODO: This flag is not yet set by anything
|
||||
const MARKED_GLOBAL = 1 << 3;
|
||||
/// TODO: This flag is not yet set by anything
|
||||
const MARKED_NONLOCAL = 1 << 4;
|
||||
const IS_INSTANCE_ATTRIBUTE = 1 << 5;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PlaceSegment<'a> {
|
||||
/// A first segment of a place expression (root name), e.g. `x` in `x.y.z[0]`.
|
||||
Name(&'a ast::name::Name),
|
||||
Member(&'a ast::name::Name),
|
||||
IntSubscript(&'a ast::Int),
|
||||
StringSubscript(&'a str),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct PlaceSegments<'a> {
|
||||
root_name: Option<&'a ast::name::Name>,
|
||||
sub_segments: &'a [PlaceExprSubSegment],
|
||||
}
|
||||
|
||||
impl<'a> Iterator for PlaceSegments<'a> {
|
||||
type Item = PlaceSegment<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(name) = self.root_name.take() {
|
||||
return Some(PlaceSegment::Name(name));
|
||||
}
|
||||
if self.sub_segments.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let segment = &self.sub_segments[0];
|
||||
self.sub_segments = &self.sub_segments[1..];
|
||||
Some(match segment {
|
||||
PlaceExprSubSegment::Member(name) => PlaceSegment::Member(name),
|
||||
PlaceExprSubSegment::IntSubscript(int) => PlaceSegment::IntSubscript(int),
|
||||
PlaceExprSubSegment::StringSubscript(string) => PlaceSegment::StringSubscript(string),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// ID that uniquely identifies a place in a file.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct FilePlaceId {
|
||||
scope: FileScopeId,
|
||||
scoped_place_id: ScopedPlaceId,
|
||||
}
|
||||
|
||||
impl FilePlaceId {
|
||||
pub fn scope(self) -> FileScopeId {
|
||||
self.scope
|
||||
}
|
||||
|
||||
pub(crate) fn scoped_place_id(self) -> ScopedPlaceId {
|
||||
self.scoped_place_id
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FilePlaceId> for ScopedPlaceId {
|
||||
fn from(val: FilePlaceId) -> Self {
|
||||
val.scoped_place_id()
|
||||
}
|
||||
}
|
||||
|
||||
/// ID that uniquely identifies a place inside a [`Scope`].
|
||||
#[newtype_index]
|
||||
#[derive(salsa::Update)]
|
||||
pub struct ScopedPlaceId;
|
||||
|
||||
/// A cross-module identifier of a scope that can be used as a salsa query parameter.
|
||||
#[salsa::tracked(debug)]
|
||||
pub struct ScopeId<'db> {
|
||||
pub file: File,
|
||||
|
||||
pub file_scope_id: FileScopeId,
|
||||
|
||||
count: countme::Count<ScopeId<'static>>,
|
||||
}
|
||||
|
||||
impl<'db> ScopeId<'db> {
|
||||
pub(crate) fn is_function_like(self, db: &'db dyn Db) -> bool {
|
||||
self.node(db).scope_kind().is_function_like()
|
||||
}
|
||||
|
||||
pub(crate) fn is_type_parameter(self, db: &'db dyn Db) -> bool {
|
||||
self.node(db).scope_kind().is_type_parameter()
|
||||
}
|
||||
|
||||
pub(crate) fn node(self, db: &dyn Db) -> &NodeWithScopeKind {
|
||||
self.scope(db).node()
|
||||
}
|
||||
|
||||
pub(crate) fn scope(self, db: &dyn Db) -> &Scope {
|
||||
semantic_index(db, self.file(db)).scope(self.file_scope_id(db))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn name(self, db: &'db dyn Db) -> &'db str {
|
||||
match self.node(db) {
|
||||
NodeWithScopeKind::Module => "<module>",
|
||||
NodeWithScopeKind::Class(class) | NodeWithScopeKind::ClassTypeParameters(class) => {
|
||||
class.name.as_str()
|
||||
}
|
||||
NodeWithScopeKind::Function(function)
|
||||
| NodeWithScopeKind::FunctionTypeParameters(function) => function.name.as_str(),
|
||||
NodeWithScopeKind::TypeAlias(type_alias)
|
||||
| NodeWithScopeKind::TypeAliasTypeParameters(type_alias) => type_alias
|
||||
.name
|
||||
.as_name_expr()
|
||||
.map(|name| name.id.as_str())
|
||||
.unwrap_or("<type alias>"),
|
||||
NodeWithScopeKind::Lambda(_) => "<lambda>",
|
||||
NodeWithScopeKind::ListComprehension(_) => "<listcomp>",
|
||||
NodeWithScopeKind::SetComprehension(_) => "<setcomp>",
|
||||
NodeWithScopeKind::DictComprehension(_) => "<dictcomp>",
|
||||
NodeWithScopeKind::GeneratorExpression(_) => "<generator>",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ID that uniquely identifies a scope inside of a module.
|
||||
#[newtype_index]
|
||||
#[derive(salsa::Update)]
|
||||
pub struct FileScopeId;
|
||||
|
||||
impl FileScopeId {
|
||||
/// Returns the scope id of the module-global scope.
|
||||
pub fn global() -> Self {
|
||||
FileScopeId::from_u32(0)
|
||||
}
|
||||
|
||||
pub fn is_global(self) -> bool {
|
||||
self == FileScopeId::global()
|
||||
}
|
||||
|
||||
pub fn to_scope_id(self, db: &dyn Db, file: File) -> ScopeId<'_> {
|
||||
let index = semantic_index(db, file);
|
||||
index.scope_ids_by_scope[self]
|
||||
}
|
||||
|
||||
pub(crate) fn is_generator_function(self, index: &SemanticIndex) -> bool {
|
||||
index.generator_functions.contains(&self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, salsa::Update)]
|
||||
pub struct Scope {
|
||||
parent: Option<FileScopeId>,
|
||||
node: NodeWithScopeKind,
|
||||
descendants: Range<FileScopeId>,
|
||||
reachability: ScopedVisibilityConstraintId,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
pub(super) fn new(
|
||||
parent: Option<FileScopeId>,
|
||||
node: NodeWithScopeKind,
|
||||
descendants: Range<FileScopeId>,
|
||||
reachability: ScopedVisibilityConstraintId,
|
||||
) -> Self {
|
||||
Scope {
|
||||
parent,
|
||||
node,
|
||||
descendants,
|
||||
reachability,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parent(&self) -> Option<FileScopeId> {
|
||||
self.parent
|
||||
}
|
||||
|
||||
pub fn node(&self) -> &NodeWithScopeKind {
|
||||
&self.node
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> ScopeKind {
|
||||
self.node().scope_kind()
|
||||
}
|
||||
|
||||
pub fn descendants(&self) -> Range<FileScopeId> {
|
||||
self.descendants.clone()
|
||||
}
|
||||
|
||||
pub(super) fn extend_descendants(&mut self, children_end: FileScopeId) {
|
||||
self.descendants = self.descendants.start..children_end;
|
||||
}
|
||||
|
||||
pub(crate) fn is_eager(&self) -> bool {
|
||||
self.kind().is_eager()
|
||||
}
|
||||
|
||||
pub(crate) fn reachability(&self) -> ScopedVisibilityConstraintId {
|
||||
self.reachability
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ScopeKind {
|
||||
Module,
|
||||
Annotation,
|
||||
Class,
|
||||
Function,
|
||||
Lambda,
|
||||
Comprehension,
|
||||
TypeAlias,
|
||||
}
|
||||
|
||||
impl ScopeKind {
|
||||
pub(crate) fn is_eager(self) -> bool {
|
||||
match self {
|
||||
ScopeKind::Module | ScopeKind::Class | ScopeKind::Comprehension => true,
|
||||
ScopeKind::Annotation
|
||||
| ScopeKind::Function
|
||||
| ScopeKind::Lambda
|
||||
| ScopeKind::TypeAlias => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_function_like(self) -> bool {
|
||||
// Type parameter scopes behave like function scopes in terms of name resolution; CPython
|
||||
// place table also uses the term "function-like" for these scopes.
|
||||
matches!(
|
||||
self,
|
||||
ScopeKind::Annotation
|
||||
| ScopeKind::Function
|
||||
| ScopeKind::Lambda
|
||||
| ScopeKind::TypeAlias
|
||||
| ScopeKind::Comprehension
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn is_class(self) -> bool {
|
||||
matches!(self, ScopeKind::Class)
|
||||
}
|
||||
|
||||
pub(crate) fn is_type_parameter(self) -> bool {
|
||||
matches!(self, ScopeKind::Annotation | ScopeKind::TypeAlias)
|
||||
}
|
||||
}
|
||||
|
||||
/// [`PlaceExpr`] table for a specific [`Scope`].
|
||||
#[derive(Default, salsa::Update)]
|
||||
pub struct PlaceTable {
|
||||
/// The place expressions in this scope.
|
||||
places: IndexVec<ScopedPlaceId, PlaceExpr>,
|
||||
|
||||
/// The set of places.
|
||||
place_set: PlaceSet,
|
||||
}
|
||||
|
||||
impl PlaceTable {
|
||||
fn shrink_to_fit(&mut self) {
|
||||
self.places.shrink_to_fit();
|
||||
}
|
||||
|
||||
pub(crate) fn place_expr(&self, place_id: impl Into<ScopedPlaceId>) -> &PlaceExpr {
|
||||
&self.places[place_id.into()]
|
||||
}
|
||||
|
||||
/// Iterate over the "root" expressions of the place (e.g. `x.y.z`, `x.y`, `x` for `x.y.z[0]`).
|
||||
pub(crate) fn root_place_exprs(
|
||||
&self,
|
||||
place_expr: &PlaceExpr,
|
||||
) -> impl Iterator<Item = &PlaceExpr> {
|
||||
place_expr
|
||||
.root_exprs()
|
||||
.filter_map(|place_expr| self.place_by_expr(&place_expr))
|
||||
}
|
||||
|
||||
#[expect(unused)]
|
||||
pub(crate) fn place_ids(&self) -> impl Iterator<Item = ScopedPlaceId> {
|
||||
self.places.indices()
|
||||
}
|
||||
|
||||
pub fn places(&self) -> impl Iterator<Item = &PlaceExpr> {
|
||||
self.places.iter()
|
||||
}
|
||||
|
||||
pub fn symbols(&self) -> impl Iterator<Item = &PlaceExpr> {
|
||||
self.places().filter(|place_expr| place_expr.is_name())
|
||||
}
|
||||
|
||||
pub fn instance_attributes(&self) -> impl Iterator<Item = &PlaceExpr> {
|
||||
self.places()
|
||||
.filter(|place_expr| place_expr.is_instance_attribute())
|
||||
}
|
||||
|
||||
/// Returns the place named `name`.
|
||||
#[allow(unused)] // used in tests
|
||||
pub(crate) fn place_by_name(&self, name: &str) -> Option<&PlaceExpr> {
|
||||
let id = self.place_id_by_name(name)?;
|
||||
Some(self.place_expr(id))
|
||||
}
|
||||
|
||||
/// Returns the flagged place by the unflagged place expression.
|
||||
///
|
||||
/// TODO: Ideally this would take a [`PlaceSegments`] instead of [`PlaceExpr`], to avoid the
|
||||
/// awkward distinction between "flagged" (canonical) and unflagged [`PlaceExpr`]; in that
|
||||
/// world, we would only create [`PlaceExpr`] in semantic indexing; in type inference we'd
|
||||
/// create [`PlaceSegments`] if we need to look up a [`PlaceExpr`]. The [`PlaceTable`] would
|
||||
/// need to gain the ability to hash and look up by a [`PlaceSegments`].
|
||||
pub(crate) fn place_by_expr(&self, place_expr: &PlaceExpr) -> Option<&PlaceExpr> {
|
||||
let id = self.place_id_by_expr(place_expr)?;
|
||||
Some(self.place_expr(id))
|
||||
}
|
||||
|
||||
/// Returns the [`ScopedPlaceId`] of the place named `name`.
|
||||
pub(crate) fn place_id_by_name(&self, name: &str) -> Option<ScopedPlaceId> {
|
||||
let (id, ()) = self
|
||||
.place_set
|
||||
.raw_entry()
|
||||
.from_hash(Self::hash_name(name), |id| {
|
||||
self.place_expr(*id).as_name().map(Name::as_str) == Some(name)
|
||||
})?;
|
||||
|
||||
Some(*id)
|
||||
}
|
||||
|
||||
/// Returns the [`ScopedPlaceId`] of the place expression.
|
||||
pub(crate) fn place_id_by_expr(&self, place_expr: &PlaceExpr) -> Option<ScopedPlaceId> {
|
||||
let (id, ()) = self
|
||||
.place_set
|
||||
.raw_entry()
|
||||
.from_hash(Self::hash_place_expr(place_expr), |id| {
|
||||
self.place_expr(*id).segments() == place_expr.segments()
|
||||
})?;
|
||||
|
||||
Some(*id)
|
||||
}
|
||||
|
||||
pub(crate) fn place_id_by_instance_attribute_name(&self, name: &str) -> Option<ScopedPlaceId> {
|
||||
self.places
|
||||
.indices()
|
||||
.find(|id| self.places[*id].is_instance_attribute_named(name))
|
||||
}
|
||||
|
||||
fn hash_name(name: &str) -> u64 {
|
||||
let mut hasher = FxHasher::default();
|
||||
name.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
fn hash_place_expr(place_expr: &PlaceExpr) -> u64 {
|
||||
let mut hasher = FxHasher::default();
|
||||
place_expr.root_name().as_str().hash(&mut hasher);
|
||||
for segment in &place_expr.sub_segments {
|
||||
match segment {
|
||||
PlaceExprSubSegment::Member(name) => name.hash(&mut hasher),
|
||||
PlaceExprSubSegment::IntSubscript(int) => int.hash(&mut hasher),
|
||||
PlaceExprSubSegment::StringSubscript(string) => string.hash(&mut hasher),
|
||||
}
|
||||
}
|
||||
hasher.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for PlaceTable {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// We don't need to compare the place_set because the place is already captured in `PlaceExpr`.
|
||||
self.places == other.places
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for PlaceTable {}
|
||||
|
||||
impl std::fmt::Debug for PlaceTable {
|
||||
/// Exclude the `place_set` field from the debug output.
|
||||
/// It's very noisy and not useful for debugging.
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("PlaceTable")
|
||||
.field(&self.places)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(super) struct PlaceTableBuilder {
|
||||
table: PlaceTable,
|
||||
|
||||
associated_place_ids: IndexVec<ScopedPlaceId, Vec<ScopedPlaceId>>,
|
||||
}
|
||||
|
||||
impl PlaceTableBuilder {
|
||||
pub(super) fn add_symbol(&mut self, name: Name) -> (ScopedPlaceId, bool) {
|
||||
let hash = PlaceTable::hash_name(&name);
|
||||
let entry = self
|
||||
.table
|
||||
.place_set
|
||||
.raw_entry_mut()
|
||||
.from_hash(hash, |id| self.table.places[*id].as_name() == Some(&name));
|
||||
|
||||
match entry {
|
||||
RawEntryMut::Occupied(entry) => (*entry.key(), false),
|
||||
RawEntryMut::Vacant(entry) => {
|
||||
let symbol = PlaceExpr::name(name);
|
||||
|
||||
let id = self.table.places.push(symbol);
|
||||
entry.insert_with_hasher(hash, id, (), |id| {
|
||||
PlaceTable::hash_place_expr(&self.table.places[*id])
|
||||
});
|
||||
let new_id = self.associated_place_ids.push(vec![]);
|
||||
debug_assert_eq!(new_id, id);
|
||||
(id, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn add_place(&mut self, place_expr: PlaceExpr) -> (ScopedPlaceId, bool) {
|
||||
let hash = PlaceTable::hash_place_expr(&place_expr);
|
||||
let entry = self.table.place_set.raw_entry_mut().from_hash(hash, |id| {
|
||||
self.table.places[*id].segments() == place_expr.segments()
|
||||
});
|
||||
|
||||
match entry {
|
||||
RawEntryMut::Occupied(entry) => (*entry.key(), false),
|
||||
RawEntryMut::Vacant(entry) => {
|
||||
let id = self.table.places.push(place_expr);
|
||||
entry.insert_with_hasher(hash, id, (), |id| {
|
||||
PlaceTable::hash_place_expr(&self.table.places[*id])
|
||||
});
|
||||
let new_id = self.associated_place_ids.push(vec![]);
|
||||
debug_assert_eq!(new_id, id);
|
||||
for root in self.table.places[id].root_exprs() {
|
||||
if let Some(root_id) = self.table.place_id_by_expr(&root) {
|
||||
self.associated_place_ids[root_id].push(id);
|
||||
}
|
||||
}
|
||||
(id, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn mark_place_bound(&mut self, id: ScopedPlaceId) {
|
||||
self.table.places[id].insert_flags(PlaceFlags::IS_BOUND);
|
||||
}
|
||||
|
||||
pub(super) fn mark_place_declared(&mut self, id: ScopedPlaceId) {
|
||||
self.table.places[id].insert_flags(PlaceFlags::IS_DECLARED);
|
||||
}
|
||||
|
||||
pub(super) fn mark_place_used(&mut self, id: ScopedPlaceId) {
|
||||
self.table.places[id].insert_flags(PlaceFlags::IS_USED);
|
||||
}
|
||||
|
||||
pub(super) fn places(&self) -> impl Iterator<Item = &PlaceExpr> {
|
||||
self.table.places()
|
||||
}
|
||||
|
||||
pub(super) fn place_id_by_expr(&self, place_expr: &PlaceExpr) -> Option<ScopedPlaceId> {
|
||||
self.table.place_id_by_expr(place_expr)
|
||||
}
|
||||
|
||||
pub(super) fn place_expr(&self, place_id: impl Into<ScopedPlaceId>) -> &PlaceExpr {
|
||||
self.table.place_expr(place_id)
|
||||
}
|
||||
|
||||
/// Returns the place IDs associated with the place (e.g. `x.y`, `x.y.z`, `x.y.z[0]` for `x`).
|
||||
pub(super) fn associated_place_ids(
|
||||
&self,
|
||||
place: ScopedPlaceId,
|
||||
) -> impl Iterator<Item = ScopedPlaceId> {
|
||||
self.associated_place_ids[place].iter().copied()
|
||||
}
|
||||
|
||||
pub(super) fn finish(mut self) -> PlaceTable {
|
||||
self.table.shrink_to_fit();
|
||||
self.table
|
||||
}
|
||||
}
|
||||
|
||||
/// Reference to a node that introduces a new scope.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) enum NodeWithScopeRef<'a> {
|
||||
Module,
|
||||
Class(&'a ast::StmtClassDef),
|
||||
Function(&'a ast::StmtFunctionDef),
|
||||
Lambda(&'a ast::ExprLambda),
|
||||
FunctionTypeParameters(&'a ast::StmtFunctionDef),
|
||||
ClassTypeParameters(&'a ast::StmtClassDef),
|
||||
TypeAlias(&'a ast::StmtTypeAlias),
|
||||
TypeAliasTypeParameters(&'a ast::StmtTypeAlias),
|
||||
ListComprehension(&'a ast::ExprListComp),
|
||||
SetComprehension(&'a ast::ExprSetComp),
|
||||
DictComprehension(&'a ast::ExprDictComp),
|
||||
GeneratorExpression(&'a ast::ExprGenerator),
|
||||
}
|
||||
|
||||
impl NodeWithScopeRef<'_> {
|
||||
/// Converts the unowned reference to an owned [`NodeWithScopeKind`].
|
||||
///
|
||||
/// # Safety
|
||||
/// The node wrapped by `self` must be a child of `module`.
|
||||
#[expect(unsafe_code)]
|
||||
pub(super) unsafe fn to_kind(self, module: ParsedModule) -> NodeWithScopeKind {
|
||||
unsafe {
|
||||
match self {
|
||||
NodeWithScopeRef::Module => NodeWithScopeKind::Module,
|
||||
NodeWithScopeRef::Class(class) => {
|
||||
NodeWithScopeKind::Class(AstNodeRef::new(module, class))
|
||||
}
|
||||
NodeWithScopeRef::Function(function) => {
|
||||
NodeWithScopeKind::Function(AstNodeRef::new(module, function))
|
||||
}
|
||||
NodeWithScopeRef::TypeAlias(type_alias) => {
|
||||
NodeWithScopeKind::TypeAlias(AstNodeRef::new(module, type_alias))
|
||||
}
|
||||
NodeWithScopeRef::TypeAliasTypeParameters(type_alias) => {
|
||||
NodeWithScopeKind::TypeAliasTypeParameters(AstNodeRef::new(module, type_alias))
|
||||
}
|
||||
NodeWithScopeRef::Lambda(lambda) => {
|
||||
NodeWithScopeKind::Lambda(AstNodeRef::new(module, lambda))
|
||||
}
|
||||
NodeWithScopeRef::FunctionTypeParameters(function) => {
|
||||
NodeWithScopeKind::FunctionTypeParameters(AstNodeRef::new(module, function))
|
||||
}
|
||||
NodeWithScopeRef::ClassTypeParameters(class) => {
|
||||
NodeWithScopeKind::ClassTypeParameters(AstNodeRef::new(module, class))
|
||||
}
|
||||
NodeWithScopeRef::ListComprehension(comprehension) => {
|
||||
NodeWithScopeKind::ListComprehension(AstNodeRef::new(module, comprehension))
|
||||
}
|
||||
NodeWithScopeRef::SetComprehension(comprehension) => {
|
||||
NodeWithScopeKind::SetComprehension(AstNodeRef::new(module, comprehension))
|
||||
}
|
||||
NodeWithScopeRef::DictComprehension(comprehension) => {
|
||||
NodeWithScopeKind::DictComprehension(AstNodeRef::new(module, comprehension))
|
||||
}
|
||||
NodeWithScopeRef::GeneratorExpression(generator) => {
|
||||
NodeWithScopeKind::GeneratorExpression(AstNodeRef::new(module, generator))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn node_key(self) -> NodeWithScopeKey {
|
||||
match self {
|
||||
NodeWithScopeRef::Module => NodeWithScopeKey::Module,
|
||||
NodeWithScopeRef::Class(class) => NodeWithScopeKey::Class(NodeKey::from_node(class)),
|
||||
NodeWithScopeRef::Function(function) => {
|
||||
NodeWithScopeKey::Function(NodeKey::from_node(function))
|
||||
}
|
||||
NodeWithScopeRef::Lambda(lambda) => {
|
||||
NodeWithScopeKey::Lambda(NodeKey::from_node(lambda))
|
||||
}
|
||||
NodeWithScopeRef::FunctionTypeParameters(function) => {
|
||||
NodeWithScopeKey::FunctionTypeParameters(NodeKey::from_node(function))
|
||||
}
|
||||
NodeWithScopeRef::ClassTypeParameters(class) => {
|
||||
NodeWithScopeKey::ClassTypeParameters(NodeKey::from_node(class))
|
||||
}
|
||||
NodeWithScopeRef::TypeAlias(type_alias) => {
|
||||
NodeWithScopeKey::TypeAlias(NodeKey::from_node(type_alias))
|
||||
}
|
||||
NodeWithScopeRef::TypeAliasTypeParameters(type_alias) => {
|
||||
NodeWithScopeKey::TypeAliasTypeParameters(NodeKey::from_node(type_alias))
|
||||
}
|
||||
NodeWithScopeRef::ListComprehension(comprehension) => {
|
||||
NodeWithScopeKey::ListComprehension(NodeKey::from_node(comprehension))
|
||||
}
|
||||
NodeWithScopeRef::SetComprehension(comprehension) => {
|
||||
NodeWithScopeKey::SetComprehension(NodeKey::from_node(comprehension))
|
||||
}
|
||||
NodeWithScopeRef::DictComprehension(comprehension) => {
|
||||
NodeWithScopeKey::DictComprehension(NodeKey::from_node(comprehension))
|
||||
}
|
||||
NodeWithScopeRef::GeneratorExpression(generator) => {
|
||||
NodeWithScopeKey::GeneratorExpression(NodeKey::from_node(generator))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Node that introduces a new scope.
|
||||
#[derive(Clone, Debug, salsa::Update)]
|
||||
pub enum NodeWithScopeKind {
|
||||
Module,
|
||||
Class(AstNodeRef<ast::StmtClassDef>),
|
||||
ClassTypeParameters(AstNodeRef<ast::StmtClassDef>),
|
||||
Function(AstNodeRef<ast::StmtFunctionDef>),
|
||||
FunctionTypeParameters(AstNodeRef<ast::StmtFunctionDef>),
|
||||
TypeAliasTypeParameters(AstNodeRef<ast::StmtTypeAlias>),
|
||||
TypeAlias(AstNodeRef<ast::StmtTypeAlias>),
|
||||
Lambda(AstNodeRef<ast::ExprLambda>),
|
||||
ListComprehension(AstNodeRef<ast::ExprListComp>),
|
||||
SetComprehension(AstNodeRef<ast::ExprSetComp>),
|
||||
DictComprehension(AstNodeRef<ast::ExprDictComp>),
|
||||
GeneratorExpression(AstNodeRef<ast::ExprGenerator>),
|
||||
}
|
||||
|
||||
impl NodeWithScopeKind {
|
||||
pub(crate) const fn scope_kind(&self) -> ScopeKind {
|
||||
match self {
|
||||
Self::Module => ScopeKind::Module,
|
||||
Self::Class(_) => ScopeKind::Class,
|
||||
Self::Function(_) => ScopeKind::Function,
|
||||
Self::Lambda(_) => ScopeKind::Lambda,
|
||||
Self::FunctionTypeParameters(_)
|
||||
| Self::ClassTypeParameters(_)
|
||||
| Self::TypeAliasTypeParameters(_) => ScopeKind::Annotation,
|
||||
Self::TypeAlias(_) => ScopeKind::TypeAlias,
|
||||
Self::ListComprehension(_)
|
||||
| Self::SetComprehension(_)
|
||||
| Self::DictComprehension(_)
|
||||
| Self::GeneratorExpression(_) => ScopeKind::Comprehension,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_class(&self) -> &ast::StmtClassDef {
|
||||
match self {
|
||||
Self::Class(class) => class.node(),
|
||||
_ => panic!("expected class"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn as_class(&self) -> Option<&ast::StmtClassDef> {
|
||||
match self {
|
||||
Self::Class(class) => Some(class.node()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_function(&self) -> &ast::StmtFunctionDef {
|
||||
self.as_function().expect("expected function")
|
||||
}
|
||||
|
||||
pub fn expect_type_alias(&self) -> &ast::StmtTypeAlias {
|
||||
match self {
|
||||
Self::TypeAlias(type_alias) => type_alias.node(),
|
||||
_ => panic!("expected type alias"),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn as_function(&self) -> Option<&ast::StmtFunctionDef> {
|
||||
match self {
|
||||
Self::Function(function) => Some(function.node()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub(crate) enum NodeWithScopeKey {
|
||||
Module,
|
||||
Class(NodeKey),
|
||||
ClassTypeParameters(NodeKey),
|
||||
Function(NodeKey),
|
||||
FunctionTypeParameters(NodeKey),
|
||||
TypeAlias(NodeKey),
|
||||
TypeAliasTypeParameters(NodeKey),
|
||||
Lambda(NodeKey),
|
||||
ListComprehension(NodeKey),
|
||||
SetComprehension(NodeKey),
|
||||
DictComprehension(NodeKey),
|
||||
GeneratorExpression(NodeKey),
|
||||
}
|
|
@ -14,7 +14,7 @@ use ruff_python_ast::Singleton;
|
|||
use crate::db::Db;
|
||||
use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::global_scope;
|
||||
use crate::semantic_index::symbol::{FileScopeId, ScopeId, ScopedSymbolId};
|
||||
use crate::semantic_index::place::{FileScopeId, ScopeId, ScopedPlaceId};
|
||||
|
||||
// A scoped identifier for each `Predicate` in a scope.
|
||||
#[newtype_index]
|
||||
|
@ -144,13 +144,13 @@ pub(crate) struct StarImportPlaceholderPredicate<'db> {
|
|||
/// Each symbol imported by a `*` import has a separate predicate associated with it:
|
||||
/// this field identifies which symbol that is.
|
||||
///
|
||||
/// Note that a [`ScopedSymbolId`] is only meaningful if you also know the scope
|
||||
/// Note that a [`ScopedPlaceId`] is only meaningful if you also know the scope
|
||||
/// it is relative to. For this specific struct, however, there's no need to store a
|
||||
/// separate field to hold the ID of the scope. `StarImportPredicate`s are only created
|
||||
/// for valid `*`-import definitions, and valid `*`-import definitions can only ever
|
||||
/// exist in the global scope; thus, we know that the `symbol_id` here will be relative
|
||||
/// to the global scope of the importing file.
|
||||
pub(crate) symbol_id: ScopedSymbolId,
|
||||
pub(crate) symbol_id: ScopedPlaceId,
|
||||
|
||||
pub(crate) referenced_file: File,
|
||||
}
|
||||
|
|
|
@ -1,589 +0,0 @@
|
|||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::Range;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use hashbrown::hash_map::RawEntryMut;
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::parsed::ParsedModule;
|
||||
use ruff_index::{IndexVec, newtype_index};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::name::Name;
|
||||
use rustc_hash::FxHasher;
|
||||
|
||||
use crate::Db;
|
||||
use crate::ast_node_ref::AstNodeRef;
|
||||
use crate::node_key::NodeKey;
|
||||
use crate::semantic_index::visibility_constraints::ScopedVisibilityConstraintId;
|
||||
use crate::semantic_index::{SemanticIndex, SymbolMap, semantic_index};
|
||||
|
||||
#[derive(Eq, PartialEq, Debug)]
|
||||
pub struct Symbol {
|
||||
name: Name,
|
||||
flags: SymbolFlags,
|
||||
}
|
||||
|
||||
impl Symbol {
|
||||
fn new(name: Name) -> Self {
|
||||
Self {
|
||||
name,
|
||||
flags: SymbolFlags::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_flags(&mut self, flags: SymbolFlags) {
|
||||
self.flags.insert(flags);
|
||||
}
|
||||
|
||||
/// The symbol's name.
|
||||
pub fn name(&self) -> &Name {
|
||||
&self.name
|
||||
}
|
||||
|
||||
/// Is the symbol used in its containing scope?
|
||||
pub fn is_used(&self) -> bool {
|
||||
self.flags.contains(SymbolFlags::IS_USED)
|
||||
}
|
||||
|
||||
/// Is the symbol defined in its containing scope?
|
||||
pub fn is_bound(&self) -> bool {
|
||||
self.flags.contains(SymbolFlags::IS_BOUND)
|
||||
}
|
||||
|
||||
/// Is the symbol declared in its containing scope?
|
||||
pub fn is_declared(&self) -> bool {
|
||||
self.flags.contains(SymbolFlags::IS_DECLARED)
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Flags that can be queried to obtain information about a symbol in a given scope.
|
||||
///
|
||||
/// See the doc-comment at the top of [`super::use_def`] for explanations of what it
|
||||
/// means for a symbol to be *bound* as opposed to *declared*.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
struct SymbolFlags: u8 {
|
||||
const IS_USED = 1 << 0;
|
||||
const IS_BOUND = 1 << 1;
|
||||
const IS_DECLARED = 1 << 2;
|
||||
/// TODO: This flag is not yet set by anything
|
||||
const MARKED_GLOBAL = 1 << 3;
|
||||
/// TODO: This flag is not yet set by anything
|
||||
const MARKED_NONLOCAL = 1 << 4;
|
||||
}
|
||||
}
|
||||
|
||||
/// ID that uniquely identifies a symbol in a file.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct FileSymbolId {
|
||||
scope: FileScopeId,
|
||||
scoped_symbol_id: ScopedSymbolId,
|
||||
}
|
||||
|
||||
impl FileSymbolId {
|
||||
pub fn scope(self) -> FileScopeId {
|
||||
self.scope
|
||||
}
|
||||
|
||||
pub(crate) fn scoped_symbol_id(self) -> ScopedSymbolId {
|
||||
self.scoped_symbol_id
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FileSymbolId> for ScopedSymbolId {
|
||||
fn from(val: FileSymbolId) -> Self {
|
||||
val.scoped_symbol_id()
|
||||
}
|
||||
}
|
||||
|
||||
/// Symbol ID that uniquely identifies a symbol inside a [`Scope`].
|
||||
#[newtype_index]
|
||||
#[derive(salsa::Update)]
|
||||
pub struct ScopedSymbolId;
|
||||
|
||||
/// A cross-module identifier of a scope that can be used as a salsa query parameter.
|
||||
#[salsa::tracked(debug)]
|
||||
pub struct ScopeId<'db> {
|
||||
pub file: File,
|
||||
|
||||
pub file_scope_id: FileScopeId,
|
||||
|
||||
count: countme::Count<ScopeId<'static>>,
|
||||
}
|
||||
|
||||
impl<'db> ScopeId<'db> {
|
||||
pub(crate) fn is_function_like(self, db: &'db dyn Db) -> bool {
|
||||
self.node(db).scope_kind().is_function_like()
|
||||
}
|
||||
|
||||
pub(crate) fn is_type_parameter(self, db: &'db dyn Db) -> bool {
|
||||
self.node(db).scope_kind().is_type_parameter()
|
||||
}
|
||||
|
||||
pub(crate) fn node(self, db: &dyn Db) -> &NodeWithScopeKind {
|
||||
self.scope(db).node()
|
||||
}
|
||||
|
||||
pub(crate) fn scope(self, db: &dyn Db) -> &Scope {
|
||||
semantic_index(db, self.file(db)).scope(self.file_scope_id(db))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn name(self, db: &'db dyn Db) -> &'db str {
|
||||
match self.node(db) {
|
||||
NodeWithScopeKind::Module => "<module>",
|
||||
NodeWithScopeKind::Class(class) | NodeWithScopeKind::ClassTypeParameters(class) => {
|
||||
class.name.as_str()
|
||||
}
|
||||
NodeWithScopeKind::Function(function)
|
||||
| NodeWithScopeKind::FunctionTypeParameters(function) => function.name.as_str(),
|
||||
NodeWithScopeKind::TypeAlias(type_alias)
|
||||
| NodeWithScopeKind::TypeAliasTypeParameters(type_alias) => type_alias
|
||||
.name
|
||||
.as_name_expr()
|
||||
.map(|name| name.id.as_str())
|
||||
.unwrap_or("<type alias>"),
|
||||
NodeWithScopeKind::Lambda(_) => "<lambda>",
|
||||
NodeWithScopeKind::ListComprehension(_) => "<listcomp>",
|
||||
NodeWithScopeKind::SetComprehension(_) => "<setcomp>",
|
||||
NodeWithScopeKind::DictComprehension(_) => "<dictcomp>",
|
||||
NodeWithScopeKind::GeneratorExpression(_) => "<generator>",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ID that uniquely identifies a scope inside of a module.
|
||||
#[newtype_index]
|
||||
#[derive(salsa::Update)]
|
||||
pub struct FileScopeId;
|
||||
|
||||
impl FileScopeId {
|
||||
/// Returns the scope id of the module-global scope.
|
||||
pub fn global() -> Self {
|
||||
FileScopeId::from_u32(0)
|
||||
}
|
||||
|
||||
pub fn is_global(self) -> bool {
|
||||
self == FileScopeId::global()
|
||||
}
|
||||
|
||||
pub fn to_scope_id(self, db: &dyn Db, file: File) -> ScopeId<'_> {
|
||||
let index = semantic_index(db, file);
|
||||
index.scope_ids_by_scope[self]
|
||||
}
|
||||
|
||||
pub(crate) fn is_generator_function(self, index: &SemanticIndex) -> bool {
|
||||
index.generator_functions.contains(&self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, salsa::Update)]
|
||||
pub struct Scope {
|
||||
parent: Option<FileScopeId>,
|
||||
node: NodeWithScopeKind,
|
||||
descendants: Range<FileScopeId>,
|
||||
reachability: ScopedVisibilityConstraintId,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
pub(super) fn new(
|
||||
parent: Option<FileScopeId>,
|
||||
node: NodeWithScopeKind,
|
||||
descendants: Range<FileScopeId>,
|
||||
reachability: ScopedVisibilityConstraintId,
|
||||
) -> Self {
|
||||
Scope {
|
||||
parent,
|
||||
node,
|
||||
descendants,
|
||||
reachability,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parent(&self) -> Option<FileScopeId> {
|
||||
self.parent
|
||||
}
|
||||
|
||||
pub fn node(&self) -> &NodeWithScopeKind {
|
||||
&self.node
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> ScopeKind {
|
||||
self.node().scope_kind()
|
||||
}
|
||||
|
||||
pub fn descendants(&self) -> Range<FileScopeId> {
|
||||
self.descendants.clone()
|
||||
}
|
||||
|
||||
pub(super) fn extend_descendants(&mut self, children_end: FileScopeId) {
|
||||
self.descendants = self.descendants.start..children_end;
|
||||
}
|
||||
|
||||
pub(crate) fn is_eager(&self) -> bool {
|
||||
self.kind().is_eager()
|
||||
}
|
||||
|
||||
pub(crate) fn reachability(&self) -> ScopedVisibilityConstraintId {
|
||||
self.reachability
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ScopeKind {
|
||||
Module,
|
||||
Annotation,
|
||||
Class,
|
||||
Function,
|
||||
Lambda,
|
||||
Comprehension,
|
||||
TypeAlias,
|
||||
}
|
||||
|
||||
impl ScopeKind {
|
||||
pub(crate) fn is_eager(self) -> bool {
|
||||
match self {
|
||||
ScopeKind::Module | ScopeKind::Class | ScopeKind::Comprehension => true,
|
||||
ScopeKind::Annotation
|
||||
| ScopeKind::Function
|
||||
| ScopeKind::Lambda
|
||||
| ScopeKind::TypeAlias => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_function_like(self) -> bool {
|
||||
// Type parameter scopes behave like function scopes in terms of name resolution; CPython
|
||||
// symbol table also uses the term "function-like" for these scopes.
|
||||
matches!(
|
||||
self,
|
||||
ScopeKind::Annotation
|
||||
| ScopeKind::Function
|
||||
| ScopeKind::Lambda
|
||||
| ScopeKind::TypeAlias
|
||||
| ScopeKind::Comprehension
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn is_class(self) -> bool {
|
||||
matches!(self, ScopeKind::Class)
|
||||
}
|
||||
|
||||
pub(crate) fn is_type_parameter(self) -> bool {
|
||||
matches!(self, ScopeKind::Annotation | ScopeKind::TypeAlias)
|
||||
}
|
||||
}
|
||||
|
||||
/// Symbol table for a specific [`Scope`].
|
||||
#[derive(Default, salsa::Update)]
|
||||
pub struct SymbolTable {
|
||||
/// The symbols in this scope.
|
||||
symbols: IndexVec<ScopedSymbolId, Symbol>,
|
||||
|
||||
/// The symbols indexed by name.
|
||||
symbols_by_name: SymbolMap,
|
||||
}
|
||||
|
||||
impl SymbolTable {
|
||||
fn shrink_to_fit(&mut self) {
|
||||
self.symbols.shrink_to_fit();
|
||||
}
|
||||
|
||||
pub(crate) fn symbol(&self, symbol_id: impl Into<ScopedSymbolId>) -> &Symbol {
|
||||
&self.symbols[symbol_id.into()]
|
||||
}
|
||||
|
||||
#[expect(unused)]
|
||||
pub(crate) fn symbol_ids(&self) -> impl Iterator<Item = ScopedSymbolId> {
|
||||
self.symbols.indices()
|
||||
}
|
||||
|
||||
pub fn symbols(&self) -> impl Iterator<Item = &Symbol> {
|
||||
self.symbols.iter()
|
||||
}
|
||||
|
||||
/// Returns the symbol named `name`.
|
||||
pub(crate) fn symbol_by_name(&self, name: &str) -> Option<&Symbol> {
|
||||
let id = self.symbol_id_by_name(name)?;
|
||||
Some(self.symbol(id))
|
||||
}
|
||||
|
||||
/// Returns the [`ScopedSymbolId`] of the symbol named `name`.
|
||||
pub(crate) fn symbol_id_by_name(&self, name: &str) -> Option<ScopedSymbolId> {
|
||||
let (id, ()) = self
|
||||
.symbols_by_name
|
||||
.raw_entry()
|
||||
.from_hash(Self::hash_name(name), |id| {
|
||||
self.symbol(*id).name().as_str() == name
|
||||
})?;
|
||||
|
||||
Some(*id)
|
||||
}
|
||||
|
||||
fn hash_name(name: &str) -> u64 {
|
||||
let mut hasher = FxHasher::default();
|
||||
name.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for SymbolTable {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// We don't need to compare the symbols_by_name because the name is already captured in `Symbol`.
|
||||
self.symbols == other.symbols
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for SymbolTable {}
|
||||
|
||||
impl std::fmt::Debug for SymbolTable {
|
||||
/// Exclude the `symbols_by_name` field from the debug output.
|
||||
/// It's very noisy and not useful for debugging.
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("SymbolTable")
|
||||
.field(&self.symbols)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(super) struct SymbolTableBuilder {
|
||||
table: SymbolTable,
|
||||
}
|
||||
|
||||
impl SymbolTableBuilder {
|
||||
pub(super) fn add_symbol(&mut self, name: Name) -> (ScopedSymbolId, bool) {
|
||||
let hash = SymbolTable::hash_name(&name);
|
||||
let entry = self
|
||||
.table
|
||||
.symbols_by_name
|
||||
.raw_entry_mut()
|
||||
.from_hash(hash, |id| self.table.symbols[*id].name() == &name);
|
||||
|
||||
match entry {
|
||||
RawEntryMut::Occupied(entry) => (*entry.key(), false),
|
||||
RawEntryMut::Vacant(entry) => {
|
||||
let symbol = Symbol::new(name);
|
||||
|
||||
let id = self.table.symbols.push(symbol);
|
||||
entry.insert_with_hasher(hash, id, (), |id| {
|
||||
SymbolTable::hash_name(self.table.symbols[*id].name().as_str())
|
||||
});
|
||||
(id, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn mark_symbol_bound(&mut self, id: ScopedSymbolId) {
|
||||
self.table.symbols[id].insert_flags(SymbolFlags::IS_BOUND);
|
||||
}
|
||||
|
||||
pub(super) fn mark_symbol_declared(&mut self, id: ScopedSymbolId) {
|
||||
self.table.symbols[id].insert_flags(SymbolFlags::IS_DECLARED);
|
||||
}
|
||||
|
||||
pub(super) fn mark_symbol_used(&mut self, id: ScopedSymbolId) {
|
||||
self.table.symbols[id].insert_flags(SymbolFlags::IS_USED);
|
||||
}
|
||||
|
||||
pub(super) fn symbols(&self) -> impl Iterator<Item = &Symbol> {
|
||||
self.table.symbols()
|
||||
}
|
||||
|
||||
pub(super) fn symbol_id_by_name(&self, name: &str) -> Option<ScopedSymbolId> {
|
||||
self.table.symbol_id_by_name(name)
|
||||
}
|
||||
|
||||
pub(super) fn symbol(&self, symbol_id: impl Into<ScopedSymbolId>) -> &Symbol {
|
||||
self.table.symbol(symbol_id)
|
||||
}
|
||||
|
||||
pub(super) fn finish(mut self) -> SymbolTable {
|
||||
self.table.shrink_to_fit();
|
||||
self.table
|
||||
}
|
||||
}
|
||||
|
||||
/// Reference to a node that introduces a new scope.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) enum NodeWithScopeRef<'a> {
|
||||
Module,
|
||||
Class(&'a ast::StmtClassDef),
|
||||
Function(&'a ast::StmtFunctionDef),
|
||||
Lambda(&'a ast::ExprLambda),
|
||||
FunctionTypeParameters(&'a ast::StmtFunctionDef),
|
||||
ClassTypeParameters(&'a ast::StmtClassDef),
|
||||
TypeAlias(&'a ast::StmtTypeAlias),
|
||||
TypeAliasTypeParameters(&'a ast::StmtTypeAlias),
|
||||
ListComprehension(&'a ast::ExprListComp),
|
||||
SetComprehension(&'a ast::ExprSetComp),
|
||||
DictComprehension(&'a ast::ExprDictComp),
|
||||
GeneratorExpression(&'a ast::ExprGenerator),
|
||||
}
|
||||
|
||||
impl NodeWithScopeRef<'_> {
|
||||
/// Converts the unowned reference to an owned [`NodeWithScopeKind`].
|
||||
///
|
||||
/// # Safety
|
||||
/// The node wrapped by `self` must be a child of `module`.
|
||||
#[expect(unsafe_code)]
|
||||
pub(super) unsafe fn to_kind(self, module: ParsedModule) -> NodeWithScopeKind {
|
||||
unsafe {
|
||||
match self {
|
||||
NodeWithScopeRef::Module => NodeWithScopeKind::Module,
|
||||
NodeWithScopeRef::Class(class) => {
|
||||
NodeWithScopeKind::Class(AstNodeRef::new(module, class))
|
||||
}
|
||||
NodeWithScopeRef::Function(function) => {
|
||||
NodeWithScopeKind::Function(AstNodeRef::new(module, function))
|
||||
}
|
||||
NodeWithScopeRef::TypeAlias(type_alias) => {
|
||||
NodeWithScopeKind::TypeAlias(AstNodeRef::new(module, type_alias))
|
||||
}
|
||||
NodeWithScopeRef::TypeAliasTypeParameters(type_alias) => {
|
||||
NodeWithScopeKind::TypeAliasTypeParameters(AstNodeRef::new(module, type_alias))
|
||||
}
|
||||
NodeWithScopeRef::Lambda(lambda) => {
|
||||
NodeWithScopeKind::Lambda(AstNodeRef::new(module, lambda))
|
||||
}
|
||||
NodeWithScopeRef::FunctionTypeParameters(function) => {
|
||||
NodeWithScopeKind::FunctionTypeParameters(AstNodeRef::new(module, function))
|
||||
}
|
||||
NodeWithScopeRef::ClassTypeParameters(class) => {
|
||||
NodeWithScopeKind::ClassTypeParameters(AstNodeRef::new(module, class))
|
||||
}
|
||||
NodeWithScopeRef::ListComprehension(comprehension) => {
|
||||
NodeWithScopeKind::ListComprehension(AstNodeRef::new(module, comprehension))
|
||||
}
|
||||
NodeWithScopeRef::SetComprehension(comprehension) => {
|
||||
NodeWithScopeKind::SetComprehension(AstNodeRef::new(module, comprehension))
|
||||
}
|
||||
NodeWithScopeRef::DictComprehension(comprehension) => {
|
||||
NodeWithScopeKind::DictComprehension(AstNodeRef::new(module, comprehension))
|
||||
}
|
||||
NodeWithScopeRef::GeneratorExpression(generator) => {
|
||||
NodeWithScopeKind::GeneratorExpression(AstNodeRef::new(module, generator))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn node_key(self) -> NodeWithScopeKey {
|
||||
match self {
|
||||
NodeWithScopeRef::Module => NodeWithScopeKey::Module,
|
||||
NodeWithScopeRef::Class(class) => NodeWithScopeKey::Class(NodeKey::from_node(class)),
|
||||
NodeWithScopeRef::Function(function) => {
|
||||
NodeWithScopeKey::Function(NodeKey::from_node(function))
|
||||
}
|
||||
NodeWithScopeRef::Lambda(lambda) => {
|
||||
NodeWithScopeKey::Lambda(NodeKey::from_node(lambda))
|
||||
}
|
||||
NodeWithScopeRef::FunctionTypeParameters(function) => {
|
||||
NodeWithScopeKey::FunctionTypeParameters(NodeKey::from_node(function))
|
||||
}
|
||||
NodeWithScopeRef::ClassTypeParameters(class) => {
|
||||
NodeWithScopeKey::ClassTypeParameters(NodeKey::from_node(class))
|
||||
}
|
||||
NodeWithScopeRef::TypeAlias(type_alias) => {
|
||||
NodeWithScopeKey::TypeAlias(NodeKey::from_node(type_alias))
|
||||
}
|
||||
NodeWithScopeRef::TypeAliasTypeParameters(type_alias) => {
|
||||
NodeWithScopeKey::TypeAliasTypeParameters(NodeKey::from_node(type_alias))
|
||||
}
|
||||
NodeWithScopeRef::ListComprehension(comprehension) => {
|
||||
NodeWithScopeKey::ListComprehension(NodeKey::from_node(comprehension))
|
||||
}
|
||||
NodeWithScopeRef::SetComprehension(comprehension) => {
|
||||
NodeWithScopeKey::SetComprehension(NodeKey::from_node(comprehension))
|
||||
}
|
||||
NodeWithScopeRef::DictComprehension(comprehension) => {
|
||||
NodeWithScopeKey::DictComprehension(NodeKey::from_node(comprehension))
|
||||
}
|
||||
NodeWithScopeRef::GeneratorExpression(generator) => {
|
||||
NodeWithScopeKey::GeneratorExpression(NodeKey::from_node(generator))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Node that introduces a new scope.
|
||||
#[derive(Clone, Debug, salsa::Update)]
|
||||
pub enum NodeWithScopeKind {
|
||||
Module,
|
||||
Class(AstNodeRef<ast::StmtClassDef>),
|
||||
ClassTypeParameters(AstNodeRef<ast::StmtClassDef>),
|
||||
Function(AstNodeRef<ast::StmtFunctionDef>),
|
||||
FunctionTypeParameters(AstNodeRef<ast::StmtFunctionDef>),
|
||||
TypeAliasTypeParameters(AstNodeRef<ast::StmtTypeAlias>),
|
||||
TypeAlias(AstNodeRef<ast::StmtTypeAlias>),
|
||||
Lambda(AstNodeRef<ast::ExprLambda>),
|
||||
ListComprehension(AstNodeRef<ast::ExprListComp>),
|
||||
SetComprehension(AstNodeRef<ast::ExprSetComp>),
|
||||
DictComprehension(AstNodeRef<ast::ExprDictComp>),
|
||||
GeneratorExpression(AstNodeRef<ast::ExprGenerator>),
|
||||
}
|
||||
|
||||
impl NodeWithScopeKind {
|
||||
pub(crate) const fn scope_kind(&self) -> ScopeKind {
|
||||
match self {
|
||||
Self::Module => ScopeKind::Module,
|
||||
Self::Class(_) => ScopeKind::Class,
|
||||
Self::Function(_) => ScopeKind::Function,
|
||||
Self::Lambda(_) => ScopeKind::Lambda,
|
||||
Self::FunctionTypeParameters(_)
|
||||
| Self::ClassTypeParameters(_)
|
||||
| Self::TypeAliasTypeParameters(_) => ScopeKind::Annotation,
|
||||
Self::TypeAlias(_) => ScopeKind::TypeAlias,
|
||||
Self::ListComprehension(_)
|
||||
| Self::SetComprehension(_)
|
||||
| Self::DictComprehension(_)
|
||||
| Self::GeneratorExpression(_) => ScopeKind::Comprehension,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_class(&self) -> &ast::StmtClassDef {
|
||||
match self {
|
||||
Self::Class(class) => class.node(),
|
||||
_ => panic!("expected class"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn as_class(&self) -> Option<&ast::StmtClassDef> {
|
||||
match self {
|
||||
Self::Class(class) => Some(class.node()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_function(&self) -> &ast::StmtFunctionDef {
|
||||
self.as_function().expect("expected function")
|
||||
}
|
||||
|
||||
pub fn expect_type_alias(&self) -> &ast::StmtTypeAlias {
|
||||
match self {
|
||||
Self::TypeAlias(type_alias) => type_alias.node(),
|
||||
_ => panic!("expected type alias"),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn as_function(&self) -> Option<&ast::StmtFunctionDef> {
|
||||
match self {
|
||||
Self::Function(function) => Some(function.node()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub(crate) enum NodeWithScopeKey {
|
||||
Module,
|
||||
Class(NodeKey),
|
||||
ClassTypeParameters(NodeKey),
|
||||
Function(NodeKey),
|
||||
FunctionTypeParameters(NodeKey),
|
||||
TypeAlias(NodeKey),
|
||||
TypeAliasTypeParameters(NodeKey),
|
||||
Lambda(NodeKey),
|
||||
ListComprehension(NodeKey),
|
||||
SetComprehension(NodeKey),
|
||||
DictComprehension(NodeKey),
|
||||
GeneratorExpression(NodeKey),
|
||||
}
|
|
@ -1,12 +1,20 @@
|
|||
//! First, some terminology:
|
||||
//!
|
||||
//! * A "binding" gives a new value to a variable. This includes many different Python statements
|
||||
//! * A "place" is semantically a location where a value can be read or written, and syntactically,
|
||||
//! an expression that can be the target of an assignment, e.g. `x`, `x[0]`, `x.y`. (The term is
|
||||
//! borrowed from Rust). In Python syntax, an expression like `f().x` is also allowed as the
|
||||
//! target so it can be called a place, but we do not record declarations / bindings like `f().x:
|
||||
//! int`, `f().x = ...`. Type checking itself can be done by recording only assignments to names,
|
||||
//! but in order to perform type narrowing by attribute/subscript assignments, they must also be
|
||||
//! recorded.
|
||||
//!
|
||||
//! * A "binding" gives a new value to a place. This includes many different Python statements
|
||||
//! (assignment statements of course, but also imports, `def` and `class` statements, `as`
|
||||
//! clauses in `with` and `except` statements, match patterns, and others) and even one
|
||||
//! expression kind (named expressions). It notably does not include annotated assignment
|
||||
//! statements without a right-hand side value; these do not assign any new value to the
|
||||
//! variable. We consider function parameters to be bindings as well, since (from the perspective
|
||||
//! of the function's internal scope), a function parameter begins the scope bound to a value.
|
||||
//! statements without a right-hand side value; these do not assign any new value to the place.
|
||||
//! We consider function parameters to be bindings as well, since (from the perspective of the
|
||||
//! function's internal scope), a function parameter begins the scope bound to a value.
|
||||
//!
|
||||
//! * A "declaration" establishes an upper bound type for the values that a variable may be
|
||||
//! permitted to take on. Annotated assignment statements (with or without an RHS value) are
|
||||
|
@ -67,12 +75,12 @@
|
|||
//! Path(path)`, with the explicit `: Path` annotation, is permitted.
|
||||
//!
|
||||
//! The general rule is that whatever declaration(s) can reach a given binding determine the
|
||||
//! validity of that binding. If there is a path in which the symbol is not declared, that is a
|
||||
//! validity of that binding. If there is a path in which the place is not declared, that is a
|
||||
//! declaration of `Unknown`. If multiple declarations can reach a binding, we union them, but by
|
||||
//! default we also issue a type error, since this implicit union of declared types may hide an
|
||||
//! error.
|
||||
//!
|
||||
//! To support type inference, we build a map from each use of a symbol to the bindings live at
|
||||
//! To support type inference, we build a map from each use of a place to the bindings live at
|
||||
//! that use, and the type narrowing constraints that apply to each binding.
|
||||
//!
|
||||
//! Let's take this code sample:
|
||||
|
@ -103,12 +111,12 @@
|
|||
//! bindings and infer a type of `Literal[3, 4]` -- the union of `Literal[3]` and `Literal[4]` --
|
||||
//! for the second use of `x`.
|
||||
//!
|
||||
//! So that's one question our use-def map needs to answer: given a specific use of a symbol, which
|
||||
//! So that's one question our use-def map needs to answer: given a specific use of a place, which
|
||||
//! binding(s) can reach that use. In [`AstIds`](crate::semantic_index::ast_ids::AstIds) we number
|
||||
//! all uses (that means a `Name` node with `Load` context) so we have a `ScopedUseId` to
|
||||
//! efficiently represent each use.
|
||||
//! all uses (that means a `Name`/`ExprAttribute`/`ExprSubscript` node with `Load` context)
|
||||
//! so we have a `ScopedUseId` to efficiently represent each use.
|
||||
//!
|
||||
//! We also need to know, for a given definition of a symbol, what type narrowing constraints apply
|
||||
//! We also need to know, for a given definition of a place, what type narrowing constraints apply
|
||||
//! to it. For instance, in this code sample:
|
||||
//!
|
||||
//! ```python
|
||||
|
@ -122,70 +130,70 @@
|
|||
//! can rule out the possibility that `x` is `None` here, which should give us the type
|
||||
//! `Literal[1]` for this use.
|
||||
//!
|
||||
//! For declared types, we need to be able to answer the question "given a binding to a symbol,
|
||||
//! which declarations of that symbol can reach the binding?" This allows us to emit a diagnostic
|
||||
//! For declared types, we need to be able to answer the question "given a binding to a place,
|
||||
//! which declarations of that place can reach the binding?" This allows us to emit a diagnostic
|
||||
//! if the binding is attempting to bind a value of a type that is not assignable to the declared
|
||||
//! type for that symbol, at that point in control flow.
|
||||
//! type for that place, at that point in control flow.
|
||||
//!
|
||||
//! We also need to know, given a declaration of a symbol, what the inferred type of that symbol is
|
||||
//! We also need to know, given a declaration of a place, what the inferred type of that place is
|
||||
//! at that point. This allows us to emit a diagnostic in a case like `x = "foo"; x: int`. The
|
||||
//! binding `x = "foo"` occurs before the declaration `x: int`, so according to our
|
||||
//! control-flow-sensitive interpretation of declarations, the assignment is not an error. But the
|
||||
//! declaration is an error, since it would violate the "inferred type must be assignable to
|
||||
//! declared type" rule.
|
||||
//!
|
||||
//! Another case we need to handle is when a symbol is referenced from a different scope (for
|
||||
//! example, an import or a nonlocal reference). We call this "public" use of a symbol. For public
|
||||
//! use of a symbol, we prefer the declared type, if there are any declarations of that symbol; if
|
||||
//! Another case we need to handle is when a place is referenced from a different scope (for
|
||||
//! example, an import or a nonlocal reference). We call this "public" use of a place. For public
|
||||
//! use of a place, we prefer the declared type, if there are any declarations of that place; if
|
||||
//! not, we fall back to the inferred type. So we also need to know which declarations and bindings
|
||||
//! can reach the end of the scope.
|
||||
//!
|
||||
//! Technically, public use of a symbol could occur from any point in control flow of the scope
|
||||
//! where the symbol is defined (via inline imports and import cycles, in the case of an import, or
|
||||
//! via a function call partway through the local scope that ends up using a symbol from the scope
|
||||
//! Technically, public use of a place could occur from any point in control flow of the scope
|
||||
//! where the place is defined (via inline imports and import cycles, in the case of an import, or
|
||||
//! via a function call partway through the local scope that ends up using a place from the scope
|
||||
//! via a global or nonlocal reference.) But modeling this fully accurately requires whole-program
|
||||
//! analysis that isn't tractable for an efficient analysis, since it means a given symbol could
|
||||
//! analysis that isn't tractable for an efficient analysis, since it means a given place could
|
||||
//! have a different type every place it's referenced throughout the program, depending on the
|
||||
//! shape of arbitrarily-sized call/import graphs. So we follow other Python type checkers in
|
||||
//! making the simplifying assumption that usually the scope will finish execution before its
|
||||
//! symbols are made visible to other scopes; for instance, most imports will import from a
|
||||
//! places are made visible to other scopes; for instance, most imports will import from a
|
||||
//! complete module, not a partially-executed module. (We may want to get a little smarter than
|
||||
//! this in the future for some closures, but for now this is where we start.)
|
||||
//!
|
||||
//! The data structure we build to answer these questions is the `UseDefMap`. It has a
|
||||
//! `bindings_by_use` vector of [`SymbolBindings`] indexed by [`ScopedUseId`], a
|
||||
//! `declarations_by_binding` vector of [`SymbolDeclarations`] indexed by [`ScopedDefinitionId`], a
|
||||
//! `bindings_by_declaration` vector of [`SymbolBindings`] indexed by [`ScopedDefinitionId`], and
|
||||
//! `public_bindings` and `public_definitions` vectors indexed by [`ScopedSymbolId`]. The values in
|
||||
//! `bindings_by_use` vector of [`Bindings`] indexed by [`ScopedUseId`], a
|
||||
//! `declarations_by_binding` vector of [`Declarations`] indexed by [`ScopedDefinitionId`], a
|
||||
//! `bindings_by_declaration` vector of [`Bindings`] indexed by [`ScopedDefinitionId`], and
|
||||
//! `public_bindings` and `public_definitions` vectors indexed by [`ScopedPlaceId`]. The values in
|
||||
//! each of these vectors are (in principle) a list of live bindings at that use/definition, or at
|
||||
//! the end of the scope for that symbol, with a list of the dominating constraints for each
|
||||
//! the end of the scope for that place, with a list of the dominating constraints for each
|
||||
//! binding.
|
||||
//!
|
||||
//! In order to avoid vectors-of-vectors-of-vectors and all the allocations that would entail, we
|
||||
//! don't actually store these "list of visible definitions" as a vector of [`Definition`].
|
||||
//! Instead, [`SymbolBindings`] and [`SymbolDeclarations`] are structs which use bit-sets to track
|
||||
//! Instead, [`Bindings`] and [`Declarations`] are structs which use bit-sets to track
|
||||
//! definitions (and constraints, in the case of bindings) in terms of [`ScopedDefinitionId`] and
|
||||
//! [`ScopedPredicateId`], which are indices into the `all_definitions` and `predicates`
|
||||
//! indexvecs in the [`UseDefMap`].
|
||||
//!
|
||||
//! There is another special kind of possible "definition" for a symbol: there might be a path from
|
||||
//! the scope entry to a given use in which the symbol is never bound. We model this with a special
|
||||
//! "unbound" definition (a `None` entry at the start of the `all_definitions` vector). If that
|
||||
//! sentinel definition is present in the live bindings at a given use, it means that there is a
|
||||
//! possible path through control flow in which that symbol is unbound. Similarly, if that sentinel
|
||||
//! is present in the live declarations, it means that the symbol is (possibly) undeclared.
|
||||
//! There is another special kind of possible "definition" for a place: there might be a path from
|
||||
//! the scope entry to a given use in which the place is never bound. We model this with a special
|
||||
//! "unbound/undeclared" definition (a [`DefinitionState::Undefined`] entry at the start of the
|
||||
//! `all_definitions` vector). If that sentinel definition is present in the live bindings at a
|
||||
//! given use, it means that there is a possible path through control flow in which that place is
|
||||
//! unbound. Similarly, if that sentinel is present in the live declarations, it means that the
|
||||
//! place is (possibly) undeclared.
|
||||
//!
|
||||
//! To build a [`UseDefMap`], the [`UseDefMapBuilder`] is notified of each new use, definition, and
|
||||
//! constraint as they are encountered by the
|
||||
//! [`SemanticIndexBuilder`](crate::semantic_index::builder::SemanticIndexBuilder) AST visit. For
|
||||
//! each symbol, the builder tracks the `SymbolState` (`SymbolBindings` and `SymbolDeclarations`)
|
||||
//! for that symbol. When we hit a use or definition of a symbol, we record the necessary parts of
|
||||
//! the current state for that symbol that we need for that use or definition. When we reach the
|
||||
//! end of the scope, it records the state for each symbol as the public definitions of that
|
||||
//! symbol.
|
||||
//! each place, the builder tracks the `PlaceState` (`Bindings` and `Declarations`) for that place.
|
||||
//! When we hit a use or definition of a place, we record the necessary parts of the current state
|
||||
//! for that place that we need for that use or definition. When we reach the end of the scope, it
|
||||
//! records the state for each place as the public definitions of that place.
|
||||
//!
|
||||
//! Let's walk through the above example. Initially we do not have any record of `x`. When we add
|
||||
//! the new symbol (before we process the first binding), we create a new undefined `SymbolState`
|
||||
//! the new place (before we process the first binding), we create a new undefined `PlaceState`
|
||||
//! which has a single live binding (the "unbound" definition) and a single live declaration (the
|
||||
//! "undeclared" definition). When we see `x = 1`, we record that as the sole live binding of `x`.
|
||||
//! The "unbound" binding is no longer visible. Then we see `x = 2`, and we replace `x = 1` as the
|
||||
|
@ -193,11 +201,11 @@
|
|||
//! of `x` are just the `x = 2` definition.
|
||||
//!
|
||||
//! Then we hit the `if` branch. We visit the `test` node (`flag` in this case), since that will
|
||||
//! happen regardless. Then we take a pre-branch snapshot of the current state for all symbols,
|
||||
//! happen regardless. Then we take a pre-branch snapshot of the current state for all places,
|
||||
//! which we'll need later. Then we record `flag` as a possible constraint on the current binding
|
||||
//! (`x = 2`), and go ahead and visit the `if` body. When we see `x = 3`, it replaces `x = 2`
|
||||
//! (constrained by `flag`) as the sole live binding of `x`. At the end of the `if` body, we take
|
||||
//! another snapshot of the current symbol state; we'll call this the post-if-body snapshot.
|
||||
//! another snapshot of the current place state; we'll call this the post-if-body snapshot.
|
||||
//!
|
||||
//! Now we need to visit the `else` clause. The conditions when entering the `else` clause should
|
||||
//! be the pre-if conditions; if we are entering the `else` clause, we know that the `if` test
|
||||
|
@ -247,7 +255,7 @@
|
|||
//! `__bool__` method of `test` returns type `bool`, we can see both bindings.
|
||||
//!
|
||||
//! Note that we also record visibility constraints for the start of the scope. This is important
|
||||
//! to determine if a symbol is definitely bound, possibly unbound, or definitely unbound. In the
|
||||
//! to determine if a place is definitely bound, possibly unbound, or definitely unbound. In the
|
||||
//! example above, The `y = <unbound>` binding is constrained by `~test`, so `y` would only be
|
||||
//! definitely-bound if `test` is always truthy.
|
||||
//!
|
||||
|
@ -259,34 +267,34 @@
|
|||
use ruff_index::{IndexVec, newtype_index};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use self::symbol_state::{
|
||||
EagerSnapshot, LiveBindingsIterator, LiveDeclaration, LiveDeclarationsIterator,
|
||||
ScopedDefinitionId, SymbolBindings, SymbolDeclarations, SymbolState,
|
||||
use self::place_state::{
|
||||
Bindings, Declarations, EagerSnapshot, LiveBindingsIterator, LiveDeclaration,
|
||||
LiveDeclarationsIterator, PlaceState, ScopedDefinitionId,
|
||||
};
|
||||
use crate::node_key::NodeKey;
|
||||
use crate::semantic_index::EagerSnapshotResult;
|
||||
use crate::semantic_index::ast_ids::ScopedUseId;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::definition::{Definition, DefinitionState};
|
||||
use crate::semantic_index::narrowing_constraints::{
|
||||
ConstraintKey, NarrowingConstraints, NarrowingConstraintsBuilder, NarrowingConstraintsIterator,
|
||||
};
|
||||
use crate::semantic_index::place::{FileScopeId, PlaceExpr, ScopeKind, ScopedPlaceId};
|
||||
use crate::semantic_index::predicate::{
|
||||
Predicate, Predicates, PredicatesBuilder, ScopedPredicateId, StarImportPlaceholderPredicate,
|
||||
};
|
||||
use crate::semantic_index::symbol::{FileScopeId, ScopeKind, ScopedSymbolId};
|
||||
use crate::semantic_index::visibility_constraints::{
|
||||
ScopedVisibilityConstraintId, VisibilityConstraints, VisibilityConstraintsBuilder,
|
||||
};
|
||||
use crate::types::{IntersectionBuilder, Truthiness, Type, infer_narrowing_constraint};
|
||||
|
||||
mod symbol_state;
|
||||
mod place_state;
|
||||
|
||||
/// Applicable definitions and constraints for every use of a name.
|
||||
#[derive(Debug, PartialEq, Eq, salsa::Update)]
|
||||
pub(crate) struct UseDefMap<'db> {
|
||||
/// Array of [`Definition`] in this scope. Only the first entry should be `None`;
|
||||
/// this represents the implicit "unbound"/"undeclared" definition of every symbol.
|
||||
all_definitions: IndexVec<ScopedDefinitionId, Option<Definition<'db>>>,
|
||||
/// Array of [`Definition`] in this scope. Only the first entry should be [`DefinitionState::Undefined`];
|
||||
/// this represents the implicit "unbound"/"undeclared" definition of every place.
|
||||
all_definitions: IndexVec<ScopedDefinitionId, DefinitionState<'db>>,
|
||||
|
||||
/// Array of predicates in this scope.
|
||||
predicates: Predicates<'db>,
|
||||
|
@ -297,34 +305,31 @@ pub(crate) struct UseDefMap<'db> {
|
|||
/// Array of visibility constraints in this scope.
|
||||
visibility_constraints: VisibilityConstraints,
|
||||
|
||||
/// [`SymbolBindings`] reaching a [`ScopedUseId`].
|
||||
bindings_by_use: IndexVec<ScopedUseId, SymbolBindings>,
|
||||
/// [`Bindings`] reaching a [`ScopedUseId`].
|
||||
bindings_by_use: IndexVec<ScopedUseId, Bindings>,
|
||||
|
||||
/// Tracks whether or not a given AST node is reachable from the start of the scope.
|
||||
node_reachability: FxHashMap<NodeKey, ScopedVisibilityConstraintId>,
|
||||
|
||||
/// If the definition is a binding (only) -- `x = 1` for example -- then we need
|
||||
/// [`SymbolDeclarations`] to know whether this binding is permitted by the live declarations.
|
||||
/// [`Declarations`] to know whether this binding is permitted by the live declarations.
|
||||
///
|
||||
/// If the definition is both a declaration and a binding -- `x: int = 1` for example -- then
|
||||
/// we don't actually need anything here, all we'll need to validate is that our own RHS is a
|
||||
/// valid assignment to our own annotation.
|
||||
declarations_by_binding: FxHashMap<Definition<'db>, SymbolDeclarations>,
|
||||
declarations_by_binding: FxHashMap<Definition<'db>, Declarations>,
|
||||
|
||||
/// If the definition is a declaration (only) -- `x: int` for example -- then we need
|
||||
/// [`SymbolBindings`] to know whether this declaration is consistent with the previously
|
||||
/// [`Bindings`] to know whether this declaration is consistent with the previously
|
||||
/// inferred type.
|
||||
///
|
||||
/// If the definition is both a declaration and a binding -- `x: int = 1` for example -- then
|
||||
/// we don't actually need anything here, all we'll need to validate is that our own RHS is a
|
||||
/// valid assignment to our own annotation.
|
||||
bindings_by_declaration: FxHashMap<Definition<'db>, SymbolBindings>,
|
||||
bindings_by_declaration: FxHashMap<Definition<'db>, Bindings>,
|
||||
|
||||
/// [`SymbolState`] visible at end of scope for each symbol.
|
||||
public_symbols: IndexVec<ScopedSymbolId, SymbolState>,
|
||||
|
||||
/// [`SymbolState`] for each instance attribute.
|
||||
instance_attributes: IndexVec<ScopedSymbolId, SymbolState>,
|
||||
/// [`PlaceState`] visible at end of scope for each place.
|
||||
public_places: IndexVec<ScopedPlaceId, PlaceState>,
|
||||
|
||||
/// Snapshot of bindings in this scope that can be used to resolve a reference in a nested
|
||||
/// eager scope.
|
||||
|
@ -402,16 +407,9 @@ impl<'db> UseDefMap<'db> {
|
|||
|
||||
pub(crate) fn public_bindings(
|
||||
&self,
|
||||
symbol: ScopedSymbolId,
|
||||
place: ScopedPlaceId,
|
||||
) -> BindingWithConstraintsIterator<'_, 'db> {
|
||||
self.bindings_iterator(self.public_symbols[symbol].bindings())
|
||||
}
|
||||
|
||||
pub(crate) fn instance_attribute_bindings(
|
||||
&self,
|
||||
symbol: ScopedSymbolId,
|
||||
) -> BindingWithConstraintsIterator<'_, 'db> {
|
||||
self.bindings_iterator(self.instance_attributes[symbol].bindings())
|
||||
self.bindings_iterator(self.public_places[place].bindings())
|
||||
}
|
||||
|
||||
pub(crate) fn eager_snapshot(
|
||||
|
@ -422,8 +420,8 @@ impl<'db> UseDefMap<'db> {
|
|||
Some(EagerSnapshot::Constraint(constraint)) => {
|
||||
EagerSnapshotResult::FoundConstraint(*constraint)
|
||||
}
|
||||
Some(EagerSnapshot::Bindings(symbol_bindings)) => {
|
||||
EagerSnapshotResult::FoundBindings(self.bindings_iterator(symbol_bindings))
|
||||
Some(EagerSnapshot::Bindings(bindings)) => {
|
||||
EagerSnapshotResult::FoundBindings(self.bindings_iterator(bindings))
|
||||
}
|
||||
None => EagerSnapshotResult::NotFound,
|
||||
}
|
||||
|
@ -445,27 +443,27 @@ impl<'db> UseDefMap<'db> {
|
|||
|
||||
pub(crate) fn public_declarations<'map>(
|
||||
&'map self,
|
||||
symbol: ScopedSymbolId,
|
||||
place: ScopedPlaceId,
|
||||
) -> DeclarationsIterator<'map, 'db> {
|
||||
let declarations = self.public_symbols[symbol].declarations();
|
||||
let declarations = self.public_places[place].declarations();
|
||||
self.declarations_iterator(declarations)
|
||||
}
|
||||
|
||||
pub(crate) fn all_public_declarations<'map>(
|
||||
&'map self,
|
||||
) -> impl Iterator<Item = (ScopedSymbolId, DeclarationsIterator<'map, 'db>)> + 'map {
|
||||
(0..self.public_symbols.len())
|
||||
.map(ScopedSymbolId::from_usize)
|
||||
.map(|symbol_id| (symbol_id, self.public_declarations(symbol_id)))
|
||||
) -> impl Iterator<Item = (ScopedPlaceId, DeclarationsIterator<'map, 'db>)> + 'map {
|
||||
(0..self.public_places.len())
|
||||
.map(ScopedPlaceId::from_usize)
|
||||
.map(|place_id| (place_id, self.public_declarations(place_id)))
|
||||
}
|
||||
|
||||
pub(crate) fn all_public_bindings<'map>(
|
||||
&'map self,
|
||||
) -> impl Iterator<Item = (ScopedSymbolId, BindingWithConstraintsIterator<'map, 'db>)> + 'map
|
||||
) -> impl Iterator<Item = (ScopedPlaceId, BindingWithConstraintsIterator<'map, 'db>)> + 'map
|
||||
{
|
||||
(0..self.public_symbols.len())
|
||||
.map(ScopedSymbolId::from_usize)
|
||||
.map(|symbol_id| (symbol_id, self.public_bindings(symbol_id)))
|
||||
(0..self.public_places.len())
|
||||
.map(ScopedPlaceId::from_usize)
|
||||
.map(|place_id| (place_id, self.public_bindings(place_id)))
|
||||
}
|
||||
|
||||
/// This function is intended to be called only once inside `TypeInferenceBuilder::infer_function_body`.
|
||||
|
@ -487,7 +485,7 @@ impl<'db> UseDefMap<'db> {
|
|||
|
||||
fn bindings_iterator<'map>(
|
||||
&'map self,
|
||||
bindings: &'map SymbolBindings,
|
||||
bindings: &'map Bindings,
|
||||
) -> BindingWithConstraintsIterator<'map, 'db> {
|
||||
BindingWithConstraintsIterator {
|
||||
all_definitions: &self.all_definitions,
|
||||
|
@ -500,7 +498,7 @@ impl<'db> UseDefMap<'db> {
|
|||
|
||||
fn declarations_iterator<'map>(
|
||||
&'map self,
|
||||
declarations: &'map SymbolDeclarations,
|
||||
declarations: &'map Declarations,
|
||||
) -> DeclarationsIterator<'map, 'db> {
|
||||
DeclarationsIterator {
|
||||
all_definitions: &self.all_definitions,
|
||||
|
@ -511,12 +509,12 @@ impl<'db> UseDefMap<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Uniquely identifies a snapshot of a symbol state that can be used to resolve a reference in a
|
||||
/// Uniquely identifies a snapshot of a place state that can be used to resolve a reference in a
|
||||
/// nested eager scope.
|
||||
///
|
||||
/// An eager scope has its entire body executed immediately at the location where it is defined.
|
||||
/// For any free references in the nested scope, we use the bindings that are visible at the point
|
||||
/// where the nested scope is defined, instead of using the public type of the symbol.
|
||||
/// where the nested scope is defined, instead of using the public type of the place.
|
||||
///
|
||||
/// There is a unique ID for each distinct [`EagerSnapshotKey`] in the file.
|
||||
#[newtype_index]
|
||||
|
@ -526,18 +524,18 @@ pub(crate) struct ScopedEagerSnapshotId;
|
|||
pub(crate) struct EagerSnapshotKey {
|
||||
/// The enclosing scope containing the bindings
|
||||
pub(crate) enclosing_scope: FileScopeId,
|
||||
/// The referenced symbol (in the enclosing scope)
|
||||
pub(crate) enclosing_symbol: ScopedSymbolId,
|
||||
/// The referenced place (in the enclosing scope)
|
||||
pub(crate) enclosing_place: ScopedPlaceId,
|
||||
/// The nested eager scope containing the reference
|
||||
pub(crate) nested_scope: FileScopeId,
|
||||
}
|
||||
|
||||
/// A snapshot of symbol states that can be used to resolve a reference in a nested eager scope.
|
||||
/// A snapshot of place states that can be used to resolve a reference in a nested eager scope.
|
||||
type EagerSnapshots = IndexVec<ScopedEagerSnapshotId, EagerSnapshot>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct BindingWithConstraintsIterator<'map, 'db> {
|
||||
all_definitions: &'map IndexVec<ScopedDefinitionId, Option<Definition<'db>>>,
|
||||
all_definitions: &'map IndexVec<ScopedDefinitionId, DefinitionState<'db>>,
|
||||
pub(crate) predicates: &'map Predicates<'db>,
|
||||
pub(crate) narrowing_constraints: &'map NarrowingConstraints,
|
||||
pub(crate) visibility_constraints: &'map VisibilityConstraints,
|
||||
|
@ -568,7 +566,7 @@ impl<'map, 'db> Iterator for BindingWithConstraintsIterator<'map, 'db> {
|
|||
impl std::iter::FusedIterator for BindingWithConstraintsIterator<'_, '_> {}
|
||||
|
||||
pub(crate) struct BindingWithConstraints<'map, 'db> {
|
||||
pub(crate) binding: Option<Definition<'db>>,
|
||||
pub(crate) binding: DefinitionState<'db>,
|
||||
pub(crate) narrowing_constraint: ConstraintsIterator<'map, 'db>,
|
||||
pub(crate) visibility_constraint: ScopedVisibilityConstraintId,
|
||||
}
|
||||
|
@ -595,10 +593,10 @@ impl<'db> ConstraintsIterator<'_, 'db> {
|
|||
self,
|
||||
db: &'db dyn crate::Db,
|
||||
base_ty: Type<'db>,
|
||||
symbol: ScopedSymbolId,
|
||||
place: ScopedPlaceId,
|
||||
) -> Type<'db> {
|
||||
let constraint_tys: Vec<_> = self
|
||||
.filter_map(|constraint| infer_narrowing_constraint(db, constraint, symbol))
|
||||
.filter_map(|constraint| infer_narrowing_constraint(db, constraint, place))
|
||||
.collect();
|
||||
|
||||
if constraint_tys.is_empty() {
|
||||
|
@ -618,14 +616,14 @@ impl<'db> ConstraintsIterator<'_, 'db> {
|
|||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct DeclarationsIterator<'map, 'db> {
|
||||
all_definitions: &'map IndexVec<ScopedDefinitionId, Option<Definition<'db>>>,
|
||||
all_definitions: &'map IndexVec<ScopedDefinitionId, DefinitionState<'db>>,
|
||||
pub(crate) predicates: &'map Predicates<'db>,
|
||||
pub(crate) visibility_constraints: &'map VisibilityConstraints,
|
||||
inner: LiveDeclarationsIterator<'map>,
|
||||
}
|
||||
|
||||
pub(crate) struct DeclarationWithConstraint<'db> {
|
||||
pub(crate) declaration: Option<Definition<'db>>,
|
||||
pub(crate) declaration: DefinitionState<'db>,
|
||||
pub(crate) visibility_constraint: ScopedVisibilityConstraintId,
|
||||
}
|
||||
|
||||
|
@ -652,8 +650,7 @@ impl std::iter::FusedIterator for DeclarationsIterator<'_, '_> {}
|
|||
/// A snapshot of the definitions and constraints state at a particular point in control flow.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(super) struct FlowSnapshot {
|
||||
symbol_states: IndexVec<ScopedSymbolId, SymbolState>,
|
||||
instance_attribute_states: IndexVec<ScopedSymbolId, SymbolState>,
|
||||
place_states: IndexVec<ScopedPlaceId, PlaceState>,
|
||||
scope_start_visibility: ScopedVisibilityConstraintId,
|
||||
reachability: ScopedVisibilityConstraintId,
|
||||
}
|
||||
|
@ -661,7 +658,7 @@ pub(super) struct FlowSnapshot {
|
|||
#[derive(Debug)]
|
||||
pub(super) struct UseDefMapBuilder<'db> {
|
||||
/// Append-only array of [`Definition`].
|
||||
all_definitions: IndexVec<ScopedDefinitionId, Option<Definition<'db>>>,
|
||||
all_definitions: IndexVec<ScopedDefinitionId, DefinitionState<'db>>,
|
||||
|
||||
/// Builder of predicates.
|
||||
pub(super) predicates: PredicatesBuilder<'db>,
|
||||
|
@ -673,7 +670,7 @@ pub(super) struct UseDefMapBuilder<'db> {
|
|||
pub(super) visibility_constraints: VisibilityConstraintsBuilder,
|
||||
|
||||
/// A constraint which describes the visibility of the unbound/undeclared state, i.e.
|
||||
/// whether or not a use of a symbol at the current point in control flow would see
|
||||
/// whether or not a use of a place at the current point in control flow would see
|
||||
/// the fake `x = <unbound>` binding at the start of the scope. This is important for
|
||||
/// cases like the following, where we need to hide the implicit unbound binding in
|
||||
/// the "else" branch:
|
||||
|
@ -688,7 +685,7 @@ pub(super) struct UseDefMapBuilder<'db> {
|
|||
pub(super) scope_start_visibility: ScopedVisibilityConstraintId,
|
||||
|
||||
/// Live bindings at each so-far-recorded use.
|
||||
bindings_by_use: IndexVec<ScopedUseId, SymbolBindings>,
|
||||
bindings_by_use: IndexVec<ScopedUseId, Bindings>,
|
||||
|
||||
/// Tracks whether or not the scope start is visible at the current point in control flow.
|
||||
/// This is subtly different from `scope_start_visibility`, as we apply these constraints
|
||||
|
@ -725,18 +722,15 @@ pub(super) struct UseDefMapBuilder<'db> {
|
|||
node_reachability: FxHashMap<NodeKey, ScopedVisibilityConstraintId>,
|
||||
|
||||
/// Live declarations for each so-far-recorded binding.
|
||||
declarations_by_binding: FxHashMap<Definition<'db>, SymbolDeclarations>,
|
||||
declarations_by_binding: FxHashMap<Definition<'db>, Declarations>,
|
||||
|
||||
/// Live bindings for each so-far-recorded declaration.
|
||||
bindings_by_declaration: FxHashMap<Definition<'db>, SymbolBindings>,
|
||||
bindings_by_declaration: FxHashMap<Definition<'db>, Bindings>,
|
||||
|
||||
/// Currently live bindings and declarations for each symbol.
|
||||
symbol_states: IndexVec<ScopedSymbolId, SymbolState>,
|
||||
/// Currently live bindings and declarations for each place.
|
||||
place_states: IndexVec<ScopedPlaceId, PlaceState>,
|
||||
|
||||
/// Currently live bindings for each instance attribute.
|
||||
instance_attribute_states: IndexVec<ScopedSymbolId, SymbolState>,
|
||||
|
||||
/// Snapshots of symbol states in this scope that can be used to resolve a reference in a
|
||||
/// Snapshots of place states in this scope that can be used to resolve a reference in a
|
||||
/// nested eager scope.
|
||||
eager_snapshots: EagerSnapshots,
|
||||
|
||||
|
@ -747,7 +741,7 @@ pub(super) struct UseDefMapBuilder<'db> {
|
|||
impl<'db> UseDefMapBuilder<'db> {
|
||||
pub(super) fn new(is_class_scope: bool) -> Self {
|
||||
Self {
|
||||
all_definitions: IndexVec::from_iter([None]),
|
||||
all_definitions: IndexVec::from_iter([DefinitionState::Undefined]),
|
||||
predicates: PredicatesBuilder::default(),
|
||||
narrowing_constraints: NarrowingConstraintsBuilder::default(),
|
||||
visibility_constraints: VisibilityConstraintsBuilder::default(),
|
||||
|
@ -757,9 +751,8 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
node_reachability: FxHashMap::default(),
|
||||
declarations_by_binding: FxHashMap::default(),
|
||||
bindings_by_declaration: FxHashMap::default(),
|
||||
symbol_states: IndexVec::new(),
|
||||
place_states: IndexVec::new(),
|
||||
eager_snapshots: EagerSnapshots::default(),
|
||||
instance_attribute_states: IndexVec::new(),
|
||||
is_class_scope,
|
||||
}
|
||||
}
|
||||
|
@ -768,38 +761,29 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
self.reachability = ScopedVisibilityConstraintId::ALWAYS_FALSE;
|
||||
}
|
||||
|
||||
pub(super) fn add_symbol(&mut self, symbol: ScopedSymbolId) {
|
||||
let new_symbol = self
|
||||
.symbol_states
|
||||
.push(SymbolState::undefined(self.scope_start_visibility));
|
||||
debug_assert_eq!(symbol, new_symbol);
|
||||
pub(super) fn add_place(&mut self, place: ScopedPlaceId) {
|
||||
let new_place = self
|
||||
.place_states
|
||||
.push(PlaceState::undefined(self.scope_start_visibility));
|
||||
debug_assert_eq!(place, new_place);
|
||||
}
|
||||
|
||||
pub(super) fn add_attribute(&mut self, symbol: ScopedSymbolId) {
|
||||
let new_symbol = self
|
||||
.instance_attribute_states
|
||||
.push(SymbolState::undefined(self.scope_start_visibility));
|
||||
debug_assert_eq!(symbol, new_symbol);
|
||||
}
|
||||
|
||||
pub(super) fn record_binding(&mut self, symbol: ScopedSymbolId, binding: Definition<'db>) {
|
||||
let def_id = self.all_definitions.push(Some(binding));
|
||||
let symbol_state = &mut self.symbol_states[symbol];
|
||||
self.declarations_by_binding
|
||||
.insert(binding, symbol_state.declarations().clone());
|
||||
symbol_state.record_binding(def_id, self.scope_start_visibility, self.is_class_scope);
|
||||
}
|
||||
|
||||
pub(super) fn record_attribute_binding(
|
||||
pub(super) fn record_binding(
|
||||
&mut self,
|
||||
symbol: ScopedSymbolId,
|
||||
place: ScopedPlaceId,
|
||||
binding: Definition<'db>,
|
||||
is_place_name: bool,
|
||||
) {
|
||||
let def_id = self.all_definitions.push(Some(binding));
|
||||
let attribute_state = &mut self.instance_attribute_states[symbol];
|
||||
let def_id = self.all_definitions.push(DefinitionState::Defined(binding));
|
||||
let place_state = &mut self.place_states[place];
|
||||
self.declarations_by_binding
|
||||
.insert(binding, attribute_state.declarations().clone());
|
||||
attribute_state.record_binding(def_id, self.scope_start_visibility, self.is_class_scope);
|
||||
.insert(binding, place_state.declarations().clone());
|
||||
place_state.record_binding(
|
||||
def_id,
|
||||
self.scope_start_visibility,
|
||||
self.is_class_scope,
|
||||
is_place_name,
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn add_predicate(&mut self, predicate: Predicate<'db>) -> ScopedPredicateId {
|
||||
|
@ -808,11 +792,7 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
|
||||
pub(super) fn record_narrowing_constraint(&mut self, predicate: ScopedPredicateId) {
|
||||
let narrowing_constraint = predicate.into();
|
||||
for state in &mut self.symbol_states {
|
||||
state
|
||||
.record_narrowing_constraint(&mut self.narrowing_constraints, narrowing_constraint);
|
||||
}
|
||||
for state in &mut self.instance_attribute_states {
|
||||
for state in &mut self.place_states {
|
||||
state
|
||||
.record_narrowing_constraint(&mut self.narrowing_constraints, narrowing_constraint);
|
||||
}
|
||||
|
@ -822,10 +802,7 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
&mut self,
|
||||
constraint: ScopedVisibilityConstraintId,
|
||||
) {
|
||||
for state in &mut self.symbol_states {
|
||||
state.record_visibility_constraint(&mut self.visibility_constraints, constraint);
|
||||
}
|
||||
for state in &mut self.instance_attribute_states {
|
||||
for state in &mut self.place_states {
|
||||
state.record_visibility_constraint(&mut self.visibility_constraints, constraint);
|
||||
}
|
||||
self.scope_start_visibility = self
|
||||
|
@ -833,13 +810,13 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
.add_and_constraint(self.scope_start_visibility, constraint);
|
||||
}
|
||||
|
||||
/// Snapshot the state of a single symbol at the current point in control flow.
|
||||
/// Snapshot the state of a single place at the current point in control flow.
|
||||
///
|
||||
/// This is only used for `*`-import visibility constraints, which are handled differently
|
||||
/// to most other visibility constraints. See the doc-comment for
|
||||
/// [`Self::record_and_negate_star_import_visibility_constraint`] for more details.
|
||||
pub(super) fn single_symbol_snapshot(&self, symbol: ScopedSymbolId) -> SymbolState {
|
||||
self.symbol_states[symbol].clone()
|
||||
pub(super) fn single_place_snapshot(&self, place: ScopedPlaceId) -> PlaceState {
|
||||
self.place_states[place].clone()
|
||||
}
|
||||
|
||||
/// This method exists solely for handling `*`-import visibility constraints.
|
||||
|
@ -863,10 +840,10 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
/// Doing things this way is cheaper in and of itself. However, it also allows us to avoid
|
||||
/// calling [`Self::simplify_visibility_constraints`] after the constraint has been applied to
|
||||
/// the "if-predicate-true" branch and negated for the "if-predicate-false" branch. Simplifying
|
||||
/// the visibility constraints is only important for symbols that did not have any new
|
||||
/// the visibility constraints is only important for places that did not have any new
|
||||
/// definitions inside either the "if-predicate-true" branch or the "if-predicate-false" branch.
|
||||
///
|
||||
/// - We only snapshot the state for a single symbol prior to the definition, rather than doing
|
||||
/// - We only snapshot the state for a single place prior to the definition, rather than doing
|
||||
/// expensive calls to [`Self::snapshot`]. Again, this is possible because we know
|
||||
/// that only a single definition occurs inside the "if-predicate-true" predicate branch.
|
||||
///
|
||||
|
@ -880,8 +857,8 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
pub(super) fn record_and_negate_star_import_visibility_constraint(
|
||||
&mut self,
|
||||
star_import: StarImportPlaceholderPredicate<'db>,
|
||||
symbol: ScopedSymbolId,
|
||||
pre_definition_state: SymbolState,
|
||||
symbol: ScopedPlaceId,
|
||||
pre_definition_state: PlaceState,
|
||||
) {
|
||||
let predicate_id = self.add_predicate(star_import.into());
|
||||
let visibility_id = self.visibility_constraints.add_atom(predicate_id);
|
||||
|
@ -890,22 +867,22 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
.add_not_constraint(visibility_id);
|
||||
|
||||
let mut post_definition_state =
|
||||
std::mem::replace(&mut self.symbol_states[symbol], pre_definition_state);
|
||||
std::mem::replace(&mut self.place_states[symbol], pre_definition_state);
|
||||
|
||||
post_definition_state
|
||||
.record_visibility_constraint(&mut self.visibility_constraints, visibility_id);
|
||||
|
||||
self.symbol_states[symbol]
|
||||
self.place_states[symbol]
|
||||
.record_visibility_constraint(&mut self.visibility_constraints, negated_visibility_id);
|
||||
|
||||
self.symbol_states[symbol].merge(
|
||||
self.place_states[symbol].merge(
|
||||
post_definition_state,
|
||||
&mut self.narrowing_constraints,
|
||||
&mut self.visibility_constraints,
|
||||
);
|
||||
}
|
||||
|
||||
/// This method resets the visibility constraints for all symbols to a previous state
|
||||
/// This method resets the visibility constraints for all places to a previous state
|
||||
/// *if* there have been no new declarations or bindings since then. Consider the
|
||||
/// following example:
|
||||
/// ```py
|
||||
|
@ -924,10 +901,7 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
/// constraint for the `x = 0` binding as well, but at the `RESET` point, we can get rid
|
||||
/// of it, as the `if`-`elif`-`elif` chain doesn't include any new bindings of `x`.
|
||||
pub(super) fn simplify_visibility_constraints(&mut self, snapshot: FlowSnapshot) {
|
||||
debug_assert!(self.symbol_states.len() >= snapshot.symbol_states.len());
|
||||
debug_assert!(
|
||||
self.instance_attribute_states.len() >= snapshot.instance_attribute_states.len()
|
||||
);
|
||||
debug_assert!(self.place_states.len() >= snapshot.place_states.len());
|
||||
|
||||
// If there are any control flow paths that have become unreachable between `snapshot` and
|
||||
// now, then it's not valid to simplify any visibility constraints to `snapshot`.
|
||||
|
@ -935,20 +909,13 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
return;
|
||||
}
|
||||
|
||||
// Note that this loop terminates when we reach a symbol not present in the snapshot.
|
||||
// This means we keep visibility constraints for all new symbols, which is intended,
|
||||
// since these symbols have been introduced in the corresponding branch, which might
|
||||
// Note that this loop terminates when we reach a place not present in the snapshot.
|
||||
// This means we keep visibility constraints for all new places, which is intended,
|
||||
// since these places have been introduced in the corresponding branch, which might
|
||||
// be subject to visibility constraints. We only simplify/reset visibility constraints
|
||||
// for symbols that have the same bindings and declarations present compared to the
|
||||
// for places that have the same bindings and declarations present compared to the
|
||||
// snapshot.
|
||||
for (current, snapshot) in self.symbol_states.iter_mut().zip(snapshot.symbol_states) {
|
||||
current.simplify_visibility_constraints(snapshot);
|
||||
}
|
||||
for (current, snapshot) in self
|
||||
.instance_attribute_states
|
||||
.iter_mut()
|
||||
.zip(snapshot.instance_attribute_states)
|
||||
{
|
||||
for (current, snapshot) in self.place_states.iter_mut().zip(snapshot.place_states) {
|
||||
current.simplify_visibility_constraints(snapshot);
|
||||
}
|
||||
}
|
||||
|
@ -965,43 +932,64 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
|
||||
pub(super) fn record_declaration(
|
||||
&mut self,
|
||||
symbol: ScopedSymbolId,
|
||||
place: ScopedPlaceId,
|
||||
declaration: Definition<'db>,
|
||||
) {
|
||||
let def_id = self.all_definitions.push(Some(declaration));
|
||||
let symbol_state = &mut self.symbol_states[symbol];
|
||||
let def_id = self
|
||||
.all_definitions
|
||||
.push(DefinitionState::Defined(declaration));
|
||||
let place_state = &mut self.place_states[place];
|
||||
self.bindings_by_declaration
|
||||
.insert(declaration, symbol_state.bindings().clone());
|
||||
symbol_state.record_declaration(def_id);
|
||||
.insert(declaration, place_state.bindings().clone());
|
||||
place_state.record_declaration(def_id);
|
||||
}
|
||||
|
||||
pub(super) fn record_declaration_and_binding(
|
||||
&mut self,
|
||||
symbol: ScopedSymbolId,
|
||||
place: ScopedPlaceId,
|
||||
definition: Definition<'db>,
|
||||
is_place_name: bool,
|
||||
) {
|
||||
// We don't need to store anything in self.bindings_by_declaration or
|
||||
// self.declarations_by_binding.
|
||||
let def_id = self.all_definitions.push(Some(definition));
|
||||
let symbol_state = &mut self.symbol_states[symbol];
|
||||
symbol_state.record_declaration(def_id);
|
||||
symbol_state.record_binding(def_id, self.scope_start_visibility, self.is_class_scope);
|
||||
let def_id = self
|
||||
.all_definitions
|
||||
.push(DefinitionState::Defined(definition));
|
||||
let place_state = &mut self.place_states[place];
|
||||
place_state.record_declaration(def_id);
|
||||
place_state.record_binding(
|
||||
def_id,
|
||||
self.scope_start_visibility,
|
||||
self.is_class_scope,
|
||||
is_place_name,
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn delete_binding(&mut self, place: ScopedPlaceId, is_place_name: bool) {
|
||||
let def_id = self.all_definitions.push(DefinitionState::Deleted);
|
||||
let place_state = &mut self.place_states[place];
|
||||
place_state.record_binding(
|
||||
def_id,
|
||||
self.scope_start_visibility,
|
||||
self.is_class_scope,
|
||||
is_place_name,
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn record_use(
|
||||
&mut self,
|
||||
symbol: ScopedSymbolId,
|
||||
place: ScopedPlaceId,
|
||||
use_id: ScopedUseId,
|
||||
node_key: NodeKey,
|
||||
) {
|
||||
// We have a use of a symbol; clone the current bindings for that symbol, and record them
|
||||
// We have a use of a place; clone the current bindings for that place, and record them
|
||||
// as the live bindings for this use.
|
||||
let new_use = self
|
||||
.bindings_by_use
|
||||
.push(self.symbol_states[symbol].bindings().clone());
|
||||
.push(self.place_states[place].bindings().clone());
|
||||
debug_assert_eq!(use_id, new_use);
|
||||
|
||||
// Track reachability of all uses of symbols to silence `unresolved-reference`
|
||||
// Track reachability of all uses of places to silence `unresolved-reference`
|
||||
// diagnostics in unreachable code.
|
||||
self.record_node_reachability(node_key);
|
||||
}
|
||||
|
@ -1012,66 +1000,59 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
|
||||
pub(super) fn snapshot_eager_state(
|
||||
&mut self,
|
||||
enclosing_symbol: ScopedSymbolId,
|
||||
enclosing_place: ScopedPlaceId,
|
||||
scope: ScopeKind,
|
||||
is_bound: bool,
|
||||
enclosing_place_expr: &PlaceExpr,
|
||||
) -> ScopedEagerSnapshotId {
|
||||
// Names bound in class scopes are never visible to nested scopes, so we never need to
|
||||
// save eager scope bindings in a class scope.
|
||||
if scope.is_class() || !is_bound {
|
||||
// Names bound in class scopes are never visible to nested scopes (but attributes/subscripts are visible),
|
||||
// so we never need to save eager scope bindings in a class scope.
|
||||
if (scope.is_class() && enclosing_place_expr.is_name()) || !enclosing_place_expr.is_bound()
|
||||
{
|
||||
self.eager_snapshots.push(EagerSnapshot::Constraint(
|
||||
self.symbol_states[enclosing_symbol]
|
||||
self.place_states[enclosing_place]
|
||||
.bindings()
|
||||
.unbound_narrowing_constraint(),
|
||||
))
|
||||
} else {
|
||||
self.eager_snapshots.push(EagerSnapshot::Bindings(
|
||||
self.symbol_states[enclosing_symbol].bindings().clone(),
|
||||
self.place_states[enclosing_place].bindings().clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Take a snapshot of the current visible-symbols state.
|
||||
/// Take a snapshot of the current visible-places state.
|
||||
pub(super) fn snapshot(&self) -> FlowSnapshot {
|
||||
FlowSnapshot {
|
||||
symbol_states: self.symbol_states.clone(),
|
||||
instance_attribute_states: self.instance_attribute_states.clone(),
|
||||
place_states: self.place_states.clone(),
|
||||
scope_start_visibility: self.scope_start_visibility,
|
||||
reachability: self.reachability,
|
||||
}
|
||||
}
|
||||
|
||||
/// Restore the current builder symbols state to the given snapshot.
|
||||
/// Restore the current builder places state to the given snapshot.
|
||||
pub(super) fn restore(&mut self, snapshot: FlowSnapshot) {
|
||||
// We never remove symbols from `symbol_states` (it's an IndexVec, and the symbol
|
||||
// IDs must line up), so the current number of known symbols must always be equal to or
|
||||
// greater than the number of known symbols in a previously-taken snapshot.
|
||||
let num_symbols = self.symbol_states.len();
|
||||
debug_assert!(num_symbols >= snapshot.symbol_states.len());
|
||||
let num_attributes = self.instance_attribute_states.len();
|
||||
debug_assert!(num_attributes >= snapshot.instance_attribute_states.len());
|
||||
// We never remove places from `place_states` (it's an IndexVec, and the place
|
||||
// IDs must line up), so the current number of known places must always be equal to or
|
||||
// greater than the number of known places in a previously-taken snapshot.
|
||||
let num_places = self.place_states.len();
|
||||
debug_assert!(num_places >= snapshot.place_states.len());
|
||||
|
||||
// Restore the current visible-definitions state to the given snapshot.
|
||||
self.symbol_states = snapshot.symbol_states;
|
||||
self.instance_attribute_states = snapshot.instance_attribute_states;
|
||||
self.place_states = snapshot.place_states;
|
||||
self.scope_start_visibility = snapshot.scope_start_visibility;
|
||||
self.reachability = snapshot.reachability;
|
||||
|
||||
// If the snapshot we are restoring is missing some symbols we've recorded since, we need
|
||||
// to fill them in so the symbol IDs continue to line up. Since they don't exist in the
|
||||
// If the snapshot we are restoring is missing some places we've recorded since, we need
|
||||
// to fill them in so the place IDs continue to line up. Since they don't exist in the
|
||||
// snapshot, the correct state to fill them in with is "undefined".
|
||||
self.symbol_states.resize(
|
||||
num_symbols,
|
||||
SymbolState::undefined(self.scope_start_visibility),
|
||||
);
|
||||
self.instance_attribute_states.resize(
|
||||
num_attributes,
|
||||
SymbolState::undefined(self.scope_start_visibility),
|
||||
self.place_states.resize(
|
||||
num_places,
|
||||
PlaceState::undefined(self.scope_start_visibility),
|
||||
);
|
||||
}
|
||||
|
||||
/// Merge the given snapshot into the current state, reflecting that we might have taken either
|
||||
/// path to get here. The new state for each symbol should include definitions from both the
|
||||
/// path to get here. The new state for each place should include definitions from both the
|
||||
/// prior state and the snapshot.
|
||||
pub(super) fn merge(&mut self, snapshot: FlowSnapshot) {
|
||||
// As an optimization, if we know statically that either of the snapshots is always
|
||||
|
@ -1089,16 +1070,13 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
return;
|
||||
}
|
||||
|
||||
// We never remove symbols from `symbol_states` (it's an IndexVec, and the symbol
|
||||
// IDs must line up), so the current number of known symbols must always be equal to or
|
||||
// greater than the number of known symbols in a previously-taken snapshot.
|
||||
debug_assert!(self.symbol_states.len() >= snapshot.symbol_states.len());
|
||||
debug_assert!(
|
||||
self.instance_attribute_states.len() >= snapshot.instance_attribute_states.len()
|
||||
);
|
||||
// We never remove places from `place_states` (it's an IndexVec, and the place
|
||||
// IDs must line up), so the current number of known places must always be equal to or
|
||||
// greater than the number of known places in a previously-taken snapshot.
|
||||
debug_assert!(self.place_states.len() >= snapshot.place_states.len());
|
||||
|
||||
let mut snapshot_definitions_iter = snapshot.symbol_states.into_iter();
|
||||
for current in &mut self.symbol_states {
|
||||
let mut snapshot_definitions_iter = snapshot.place_states.into_iter();
|
||||
for current in &mut self.place_states {
|
||||
if let Some(snapshot) = snapshot_definitions_iter.next() {
|
||||
current.merge(
|
||||
snapshot,
|
||||
|
@ -1107,27 +1085,11 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
);
|
||||
} else {
|
||||
current.merge(
|
||||
SymbolState::undefined(snapshot.scope_start_visibility),
|
||||
&mut self.narrowing_constraints,
|
||||
&mut self.visibility_constraints,
|
||||
);
|
||||
// Symbol not present in snapshot, so it's unbound/undeclared from that path.
|
||||
}
|
||||
}
|
||||
let mut snapshot_definitions_iter = snapshot.instance_attribute_states.into_iter();
|
||||
for current in &mut self.instance_attribute_states {
|
||||
if let Some(snapshot) = snapshot_definitions_iter.next() {
|
||||
current.merge(
|
||||
snapshot,
|
||||
&mut self.narrowing_constraints,
|
||||
&mut self.visibility_constraints,
|
||||
);
|
||||
} else {
|
||||
current.merge(
|
||||
SymbolState::undefined(snapshot.scope_start_visibility),
|
||||
PlaceState::undefined(snapshot.scope_start_visibility),
|
||||
&mut self.narrowing_constraints,
|
||||
&mut self.visibility_constraints,
|
||||
);
|
||||
// Place not present in snapshot, so it's unbound/undeclared from that path.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1142,8 +1104,7 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
|
||||
pub(super) fn finish(mut self) -> UseDefMap<'db> {
|
||||
self.all_definitions.shrink_to_fit();
|
||||
self.symbol_states.shrink_to_fit();
|
||||
self.instance_attribute_states.shrink_to_fit();
|
||||
self.place_states.shrink_to_fit();
|
||||
self.bindings_by_use.shrink_to_fit();
|
||||
self.node_reachability.shrink_to_fit();
|
||||
self.declarations_by_binding.shrink_to_fit();
|
||||
|
@ -1157,8 +1118,7 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
visibility_constraints: self.visibility_constraints.build(),
|
||||
bindings_by_use: self.bindings_by_use,
|
||||
node_reachability: self.node_reachability,
|
||||
public_symbols: self.symbol_states,
|
||||
instance_attributes: self.instance_attribute_states,
|
||||
public_places: self.place_states,
|
||||
declarations_by_binding: self.declarations_by_binding,
|
||||
bindings_by_declaration: self.bindings_by_declaration,
|
||||
eager_snapshots: self.eager_snapshots,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//! Track live bindings per symbol, applicable constraints per binding, and live declarations.
|
||||
//! Track live bindings per place, applicable constraints per binding, and live declarations.
|
||||
//!
|
||||
//! These data structures operate entirely on scope-local newtype-indices for definitions and
|
||||
//! constraints, referring to their location in the `all_definitions` and `all_constraints`
|
||||
|
@ -60,9 +60,9 @@ pub(super) struct ScopedDefinitionId;
|
|||
|
||||
impl ScopedDefinitionId {
|
||||
/// A special ID that is used to describe an implicit start-of-scope state. When
|
||||
/// we see that this definition is live, we know that the symbol is (possibly)
|
||||
/// we see that this definition is live, we know that the place is (possibly)
|
||||
/// unbound or undeclared at a given usage site.
|
||||
/// When creating a use-def-map builder, we always add an empty `None` definition
|
||||
/// When creating a use-def-map builder, we always add an empty `DefinitionState::Undefined` definition
|
||||
/// at index 0, so this ID is always present.
|
||||
pub(super) const UNBOUND: ScopedDefinitionId = ScopedDefinitionId::from_u32(0);
|
||||
|
||||
|
@ -71,19 +71,19 @@ impl ScopedDefinitionId {
|
|||
}
|
||||
}
|
||||
|
||||
/// Can keep inline this many live bindings or declarations per symbol at a given time; more will
|
||||
/// Can keep inline this many live bindings or declarations per place at a given time; more will
|
||||
/// go to heap.
|
||||
const INLINE_DEFINITIONS_PER_SYMBOL: usize = 4;
|
||||
const INLINE_DEFINITIONS_PER_PLACE: usize = 4;
|
||||
|
||||
/// Live declarations for a single symbol at some point in control flow, with their
|
||||
/// Live declarations for a single place at some point in control flow, with their
|
||||
/// corresponding visibility constraints.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, salsa::Update)]
|
||||
pub(super) struct SymbolDeclarations {
|
||||
/// A list of live declarations for this symbol, sorted by their `ScopedDefinitionId`
|
||||
live_declarations: SmallVec<[LiveDeclaration; INLINE_DEFINITIONS_PER_SYMBOL]>,
|
||||
pub(super) struct Declarations {
|
||||
/// A list of live declarations for this place, sorted by their `ScopedDefinitionId`
|
||||
live_declarations: SmallVec<[LiveDeclaration; INLINE_DEFINITIONS_PER_PLACE]>,
|
||||
}
|
||||
|
||||
/// One of the live declarations for a single symbol at some point in control flow.
|
||||
/// One of the live declarations for a single place at some point in control flow.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(super) struct LiveDeclaration {
|
||||
pub(super) declaration: ScopedDefinitionId,
|
||||
|
@ -92,7 +92,7 @@ pub(super) struct LiveDeclaration {
|
|||
|
||||
pub(super) type LiveDeclarationsIterator<'a> = std::slice::Iter<'a, LiveDeclaration>;
|
||||
|
||||
impl SymbolDeclarations {
|
||||
impl Declarations {
|
||||
fn undeclared(scope_start_visibility: ScopedVisibilityConstraintId) -> Self {
|
||||
let initial_declaration = LiveDeclaration {
|
||||
declaration: ScopedDefinitionId::UNBOUND,
|
||||
|
@ -103,7 +103,7 @@ impl SymbolDeclarations {
|
|||
}
|
||||
}
|
||||
|
||||
/// Record a newly-encountered declaration for this symbol.
|
||||
/// Record a newly-encountered declaration for this place.
|
||||
fn record_declaration(&mut self, declaration: ScopedDefinitionId) {
|
||||
// The new declaration replaces all previous live declaration in this path.
|
||||
self.live_declarations.clear();
|
||||
|
@ -125,17 +125,17 @@ impl SymbolDeclarations {
|
|||
}
|
||||
}
|
||||
|
||||
/// Return an iterator over live declarations for this symbol.
|
||||
/// Return an iterator over live declarations for this place.
|
||||
pub(super) fn iter(&self) -> LiveDeclarationsIterator<'_> {
|
||||
self.live_declarations.iter()
|
||||
}
|
||||
|
||||
/// Iterate over the IDs of each currently live declaration for this symbol
|
||||
/// Iterate over the IDs of each currently live declaration for this place
|
||||
fn iter_declarations(&self) -> impl Iterator<Item = ScopedDefinitionId> + '_ {
|
||||
self.iter().map(|lb| lb.declaration)
|
||||
}
|
||||
|
||||
fn simplify_visibility_constraints(&mut self, other: SymbolDeclarations) {
|
||||
fn simplify_visibility_constraints(&mut self, other: Declarations) {
|
||||
// If the set of live declarations hasn't changed, don't simplify.
|
||||
if self.live_declarations.len() != other.live_declarations.len()
|
||||
|| !self.iter_declarations().eq(other.iter_declarations())
|
||||
|
@ -181,7 +181,7 @@ impl SymbolDeclarations {
|
|||
}
|
||||
}
|
||||
|
||||
/// A snapshot of a symbol state that can be used to resolve a reference in a nested eager scope.
|
||||
/// A snapshot of a place state that can be used to resolve a reference in a nested eager scope.
|
||||
/// If there are bindings in a (non-class) scope , they are stored in `Bindings`.
|
||||
/// Even if it's a class scope (class variables are not visible to nested scopes) or there are no
|
||||
/// bindings, the current narrowing constraint is necessary for narrowing, so it's stored in
|
||||
|
@ -189,34 +189,30 @@ impl SymbolDeclarations {
|
|||
#[derive(Clone, Debug, PartialEq, Eq, salsa::Update)]
|
||||
pub(super) enum EagerSnapshot {
|
||||
Constraint(ScopedNarrowingConstraint),
|
||||
Bindings(SymbolBindings),
|
||||
Bindings(Bindings),
|
||||
}
|
||||
|
||||
/// Live bindings for a single symbol at some point in control flow. Each live binding comes
|
||||
/// Live bindings for a single place at some point in control flow. Each live binding comes
|
||||
/// with a set of narrowing constraints and a visibility constraint.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, salsa::Update)]
|
||||
pub(super) struct SymbolBindings {
|
||||
pub(super) struct Bindings {
|
||||
/// The narrowing constraint applicable to the "unbound" binding, if we need access to it even
|
||||
/// when it's not visible. This happens in class scopes, where local bindings are not visible
|
||||
/// when it's not visible. This happens in class scopes, where local name bindings are not visible
|
||||
/// to nested scopes, but we still need to know what narrowing constraints were applied to the
|
||||
/// "unbound" binding.
|
||||
unbound_narrowing_constraint: Option<ScopedNarrowingConstraint>,
|
||||
/// A list of live bindings for this symbol, sorted by their `ScopedDefinitionId`
|
||||
live_bindings: SmallVec<[LiveBinding; INLINE_DEFINITIONS_PER_SYMBOL]>,
|
||||
/// A list of live bindings for this place, sorted by their `ScopedDefinitionId`
|
||||
live_bindings: SmallVec<[LiveBinding; INLINE_DEFINITIONS_PER_PLACE]>,
|
||||
}
|
||||
|
||||
impl SymbolBindings {
|
||||
impl Bindings {
|
||||
pub(super) fn unbound_narrowing_constraint(&self) -> ScopedNarrowingConstraint {
|
||||
debug_assert!(
|
||||
self.unbound_narrowing_constraint.is_some()
|
||||
|| self.live_bindings[0].binding.is_unbound()
|
||||
);
|
||||
self.unbound_narrowing_constraint
|
||||
.unwrap_or(self.live_bindings[0].narrowing_constraint)
|
||||
}
|
||||
}
|
||||
|
||||
/// One of the live bindings for a single symbol at some point in control flow.
|
||||
/// One of the live bindings for a single place at some point in control flow.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(super) struct LiveBinding {
|
||||
pub(super) binding: ScopedDefinitionId,
|
||||
|
@ -226,7 +222,7 @@ pub(super) struct LiveBinding {
|
|||
|
||||
pub(super) type LiveBindingsIterator<'a> = std::slice::Iter<'a, LiveBinding>;
|
||||
|
||||
impl SymbolBindings {
|
||||
impl Bindings {
|
||||
fn unbound(scope_start_visibility: ScopedVisibilityConstraintId) -> Self {
|
||||
let initial_binding = LiveBinding {
|
||||
binding: ScopedDefinitionId::UNBOUND,
|
||||
|
@ -239,16 +235,17 @@ impl SymbolBindings {
|
|||
}
|
||||
}
|
||||
|
||||
/// Record a newly-encountered binding for this symbol.
|
||||
/// Record a newly-encountered binding for this place.
|
||||
pub(super) fn record_binding(
|
||||
&mut self,
|
||||
binding: ScopedDefinitionId,
|
||||
visibility_constraint: ScopedVisibilityConstraintId,
|
||||
is_class_scope: bool,
|
||||
is_place_name: bool,
|
||||
) {
|
||||
// If we are in a class scope, and the unbound binding was previously visible, but we will
|
||||
// If we are in a class scope, and the unbound name binding was previously visible, but we will
|
||||
// now replace it, record the narrowing constraints on it:
|
||||
if is_class_scope && self.live_bindings[0].binding.is_unbound() {
|
||||
if is_class_scope && is_place_name && self.live_bindings[0].binding.is_unbound() {
|
||||
self.unbound_narrowing_constraint = Some(self.live_bindings[0].narrowing_constraint);
|
||||
}
|
||||
// The new binding replaces all previous live bindings in this path, and has no
|
||||
|
@ -285,17 +282,17 @@ impl SymbolBindings {
|
|||
}
|
||||
}
|
||||
|
||||
/// Iterate over currently live bindings for this symbol
|
||||
/// Iterate over currently live bindings for this place
|
||||
pub(super) fn iter(&self) -> LiveBindingsIterator<'_> {
|
||||
self.live_bindings.iter()
|
||||
}
|
||||
|
||||
/// Iterate over the IDs of each currently live binding for this symbol
|
||||
/// Iterate over the IDs of each currently live binding for this place
|
||||
fn iter_bindings(&self) -> impl Iterator<Item = ScopedDefinitionId> + '_ {
|
||||
self.iter().map(|lb| lb.binding)
|
||||
}
|
||||
|
||||
fn simplify_visibility_constraints(&mut self, other: SymbolBindings) {
|
||||
fn simplify_visibility_constraints(&mut self, other: Bindings) {
|
||||
// If the set of live bindings hasn't changed, don't simplify.
|
||||
if self.live_bindings.len() != other.live_bindings.len()
|
||||
|| !self.iter_bindings().eq(other.iter_bindings())
|
||||
|
@ -360,30 +357,35 @@ impl SymbolBindings {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(in crate::semantic_index) struct SymbolState {
|
||||
declarations: SymbolDeclarations,
|
||||
bindings: SymbolBindings,
|
||||
pub(in crate::semantic_index) struct PlaceState {
|
||||
declarations: Declarations,
|
||||
bindings: Bindings,
|
||||
}
|
||||
|
||||
impl SymbolState {
|
||||
/// Return a new [`SymbolState`] representing an unbound, undeclared symbol.
|
||||
impl PlaceState {
|
||||
/// Return a new [`PlaceState`] representing an unbound, undeclared place.
|
||||
pub(super) fn undefined(scope_start_visibility: ScopedVisibilityConstraintId) -> Self {
|
||||
Self {
|
||||
declarations: SymbolDeclarations::undeclared(scope_start_visibility),
|
||||
bindings: SymbolBindings::unbound(scope_start_visibility),
|
||||
declarations: Declarations::undeclared(scope_start_visibility),
|
||||
bindings: Bindings::unbound(scope_start_visibility),
|
||||
}
|
||||
}
|
||||
|
||||
/// Record a newly-encountered binding for this symbol.
|
||||
/// Record a newly-encountered binding for this place.
|
||||
pub(super) fn record_binding(
|
||||
&mut self,
|
||||
binding_id: ScopedDefinitionId,
|
||||
visibility_constraint: ScopedVisibilityConstraintId,
|
||||
is_class_scope: bool,
|
||||
is_place_name: bool,
|
||||
) {
|
||||
debug_assert_ne!(binding_id, ScopedDefinitionId::UNBOUND);
|
||||
self.bindings
|
||||
.record_binding(binding_id, visibility_constraint, is_class_scope);
|
||||
self.bindings.record_binding(
|
||||
binding_id,
|
||||
visibility_constraint,
|
||||
is_class_scope,
|
||||
is_place_name,
|
||||
);
|
||||
}
|
||||
|
||||
/// Add given constraint to all live bindings.
|
||||
|
@ -409,24 +411,24 @@ impl SymbolState {
|
|||
}
|
||||
|
||||
/// Simplifies this snapshot to have the same visibility constraints as a previous point in the
|
||||
/// control flow, but only if the set of live bindings or declarations for this symbol hasn't
|
||||
/// control flow, but only if the set of live bindings or declarations for this place hasn't
|
||||
/// changed.
|
||||
pub(super) fn simplify_visibility_constraints(&mut self, snapshot_state: SymbolState) {
|
||||
pub(super) fn simplify_visibility_constraints(&mut self, snapshot_state: PlaceState) {
|
||||
self.bindings
|
||||
.simplify_visibility_constraints(snapshot_state.bindings);
|
||||
self.declarations
|
||||
.simplify_visibility_constraints(snapshot_state.declarations);
|
||||
}
|
||||
|
||||
/// Record a newly-encountered declaration of this symbol.
|
||||
/// Record a newly-encountered declaration of this place.
|
||||
pub(super) fn record_declaration(&mut self, declaration_id: ScopedDefinitionId) {
|
||||
self.declarations.record_declaration(declaration_id);
|
||||
}
|
||||
|
||||
/// Merge another [`SymbolState`] into this one.
|
||||
/// Merge another [`PlaceState`] into this one.
|
||||
pub(super) fn merge(
|
||||
&mut self,
|
||||
b: SymbolState,
|
||||
b: PlaceState,
|
||||
narrowing_constraints: &mut NarrowingConstraintsBuilder,
|
||||
visibility_constraints: &mut VisibilityConstraintsBuilder,
|
||||
) {
|
||||
|
@ -436,11 +438,11 @@ impl SymbolState {
|
|||
.merge(b.declarations, visibility_constraints);
|
||||
}
|
||||
|
||||
pub(super) fn bindings(&self) -> &SymbolBindings {
|
||||
pub(super) fn bindings(&self) -> &Bindings {
|
||||
&self.bindings
|
||||
}
|
||||
|
||||
pub(super) fn declarations(&self) -> &SymbolDeclarations {
|
||||
pub(super) fn declarations(&self) -> &Declarations {
|
||||
&self.declarations
|
||||
}
|
||||
}
|
||||
|
@ -454,10 +456,10 @@ mod tests {
|
|||
#[track_caller]
|
||||
fn assert_bindings(
|
||||
narrowing_constraints: &NarrowingConstraintsBuilder,
|
||||
symbol: &SymbolState,
|
||||
place: &PlaceState,
|
||||
expected: &[&str],
|
||||
) {
|
||||
let actual = symbol
|
||||
let actual = place
|
||||
.bindings()
|
||||
.iter()
|
||||
.map(|live_binding| {
|
||||
|
@ -479,8 +481,8 @@ mod tests {
|
|||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) fn assert_declarations(symbol: &SymbolState, expected: &[&str]) {
|
||||
let actual = symbol
|
||||
pub(crate) fn assert_declarations(place: &PlaceState, expected: &[&str]) {
|
||||
let actual = place
|
||||
.declarations()
|
||||
.iter()
|
||||
.map(
|
||||
|
@ -502,7 +504,7 @@ mod tests {
|
|||
#[test]
|
||||
fn unbound() {
|
||||
let narrowing_constraints = NarrowingConstraintsBuilder::default();
|
||||
let sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
let sym = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
|
||||
assert_bindings(&narrowing_constraints, &sym, &["unbound<>"]);
|
||||
}
|
||||
|
@ -510,11 +512,12 @@ mod tests {
|
|||
#[test]
|
||||
fn with() {
|
||||
let narrowing_constraints = NarrowingConstraintsBuilder::default();
|
||||
let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
let mut sym = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
sym.record_binding(
|
||||
ScopedDefinitionId::from_u32(1),
|
||||
ScopedVisibilityConstraintId::ALWAYS_TRUE,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
|
||||
assert_bindings(&narrowing_constraints, &sym, &["1<>"]);
|
||||
|
@ -523,11 +526,12 @@ mod tests {
|
|||
#[test]
|
||||
fn record_constraint() {
|
||||
let mut narrowing_constraints = NarrowingConstraintsBuilder::default();
|
||||
let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
let mut sym = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
sym.record_binding(
|
||||
ScopedDefinitionId::from_u32(1),
|
||||
ScopedVisibilityConstraintId::ALWAYS_TRUE,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
let predicate = ScopedPredicateId::from_u32(0).into();
|
||||
sym.record_narrowing_constraint(&mut narrowing_constraints, predicate);
|
||||
|
@ -541,20 +545,22 @@ mod tests {
|
|||
let mut visibility_constraints = VisibilityConstraintsBuilder::default();
|
||||
|
||||
// merging the same definition with the same constraint keeps the constraint
|
||||
let mut sym1a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
let mut sym1a = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
sym1a.record_binding(
|
||||
ScopedDefinitionId::from_u32(1),
|
||||
ScopedVisibilityConstraintId::ALWAYS_TRUE,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
let predicate = ScopedPredicateId::from_u32(0).into();
|
||||
sym1a.record_narrowing_constraint(&mut narrowing_constraints, predicate);
|
||||
|
||||
let mut sym1b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
let mut sym1b = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
sym1b.record_binding(
|
||||
ScopedDefinitionId::from_u32(1),
|
||||
ScopedVisibilityConstraintId::ALWAYS_TRUE,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
let predicate = ScopedPredicateId::from_u32(0).into();
|
||||
sym1b.record_narrowing_constraint(&mut narrowing_constraints, predicate);
|
||||
|
@ -568,20 +574,22 @@ mod tests {
|
|||
assert_bindings(&narrowing_constraints, &sym1, &["1<0>"]);
|
||||
|
||||
// merging the same definition with differing constraints drops all constraints
|
||||
let mut sym2a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
let mut sym2a = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
sym2a.record_binding(
|
||||
ScopedDefinitionId::from_u32(2),
|
||||
ScopedVisibilityConstraintId::ALWAYS_TRUE,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
let predicate = ScopedPredicateId::from_u32(1).into();
|
||||
sym2a.record_narrowing_constraint(&mut narrowing_constraints, predicate);
|
||||
|
||||
let mut sym1b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
let mut sym1b = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
sym1b.record_binding(
|
||||
ScopedDefinitionId::from_u32(2),
|
||||
ScopedVisibilityConstraintId::ALWAYS_TRUE,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
let predicate = ScopedPredicateId::from_u32(2).into();
|
||||
sym1b.record_narrowing_constraint(&mut narrowing_constraints, predicate);
|
||||
|
@ -595,16 +603,17 @@ mod tests {
|
|||
assert_bindings(&narrowing_constraints, &sym2, &["2<>"]);
|
||||
|
||||
// merging a constrained definition with unbound keeps both
|
||||
let mut sym3a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
let mut sym3a = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
sym3a.record_binding(
|
||||
ScopedDefinitionId::from_u32(3),
|
||||
ScopedVisibilityConstraintId::ALWAYS_TRUE,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
let predicate = ScopedPredicateId::from_u32(3).into();
|
||||
sym3a.record_narrowing_constraint(&mut narrowing_constraints, predicate);
|
||||
|
||||
let sym2b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
let sym2b = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
|
||||
sym3a.merge(
|
||||
sym2b,
|
||||
|
@ -626,14 +635,14 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn no_declaration() {
|
||||
let sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
let sym = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
|
||||
assert_declarations(&sym, &["undeclared"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_declaration() {
|
||||
let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
let mut sym = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
|
||||
assert_declarations(&sym, &["1"]);
|
||||
|
@ -641,7 +650,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn record_declaration_override() {
|
||||
let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
let mut sym = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(2));
|
||||
|
||||
|
@ -652,10 +661,10 @@ mod tests {
|
|||
fn record_declaration_merge() {
|
||||
let mut narrowing_constraints = NarrowingConstraintsBuilder::default();
|
||||
let mut visibility_constraints = VisibilityConstraintsBuilder::default();
|
||||
let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
let mut sym = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
|
||||
let mut sym2 = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
let mut sym2 = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
sym2.record_declaration(ScopedDefinitionId::from_u32(2));
|
||||
|
||||
sym.merge(
|
||||
|
@ -671,10 +680,10 @@ mod tests {
|
|||
fn record_declaration_merge_partial_undeclared() {
|
||||
let mut narrowing_constraints = NarrowingConstraintsBuilder::default();
|
||||
let mut visibility_constraints = VisibilityConstraintsBuilder::default();
|
||||
let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
let mut sym = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
|
||||
let sym2 = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
let sym2 = PlaceState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
|
||||
sym.merge(
|
||||
sym2,
|
|
@ -180,12 +180,12 @@ use rustc_hash::FxHashMap;
|
|||
|
||||
use crate::Db;
|
||||
use crate::dunder_all::dunder_all_names;
|
||||
use crate::place::{RequiresExplicitReExport, imported_symbol};
|
||||
use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::place_table;
|
||||
use crate::semantic_index::predicate::{
|
||||
PatternPredicate, PatternPredicateKind, Predicate, PredicateNode, Predicates, ScopedPredicateId,
|
||||
};
|
||||
use crate::semantic_index::symbol_table;
|
||||
use crate::symbol::{RequiresExplicitReExport, imported_symbol};
|
||||
use crate::types::{Truthiness, Type, infer_expression_type};
|
||||
|
||||
/// A ternary formula that defines under what conditions a binding is visible. (A ternary formula
|
||||
|
@ -654,8 +654,10 @@ impl VisibilityConstraints {
|
|||
}
|
||||
PredicateNode::Pattern(inner) => Self::analyze_single_pattern_predicate(db, inner),
|
||||
PredicateNode::StarImportPlaceholder(star_import) => {
|
||||
let symbol_table = symbol_table(db, star_import.scope(db));
|
||||
let symbol_name = symbol_table.symbol(star_import.symbol_id(db)).name();
|
||||
let place_table = place_table(db, star_import.scope(db));
|
||||
let symbol_name = place_table
|
||||
.place_expr(star_import.symbol_id(db))
|
||||
.expect_name();
|
||||
let referenced_file = star_import.referenced_file(db);
|
||||
|
||||
let requires_explicit_reexport = match dunder_all_names(db, referenced_file) {
|
||||
|
@ -675,15 +677,15 @@ impl VisibilityConstraints {
|
|||
};
|
||||
|
||||
match imported_symbol(db, referenced_file, symbol_name, requires_explicit_reexport)
|
||||
.symbol
|
||||
.place
|
||||
{
|
||||
crate::symbol::Symbol::Type(_, crate::symbol::Boundness::Bound) => {
|
||||
crate::place::Place::Type(_, crate::place::Boundness::Bound) => {
|
||||
Truthiness::AlwaysTrue
|
||||
}
|
||||
crate::symbol::Symbol::Type(_, crate::symbol::Boundness::PossiblyUnbound) => {
|
||||
crate::place::Place::Type(_, crate::place::Boundness::PossiblyUnbound) => {
|
||||
Truthiness::Ambiguous
|
||||
}
|
||||
crate::symbol::Symbol::Unbound => Truthiness::AlwaysFalse,
|
||||
crate::place::Place::Unbound => Truthiness::AlwaysFalse,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ use crate::Db;
|
|||
use crate::module_name::ModuleName;
|
||||
use crate::module_resolver::{Module, resolve_module};
|
||||
use crate::semantic_index::ast_ids::HasScopedExpressionId;
|
||||
use crate::semantic_index::place::FileScopeId;
|
||||
use crate::semantic_index::semantic_index;
|
||||
use crate::semantic_index::symbol::FileScopeId;
|
||||
use crate::types::ide_support::all_declarations_and_bindings;
|
||||
use crate::types::{Type, binding_type, infer_scope_types};
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -12,7 +12,7 @@ use super::{
|
|||
};
|
||||
use crate::db::Db;
|
||||
use crate::dunder_all::dunder_all_names;
|
||||
use crate::symbol::{Boundness, Symbol};
|
||||
use crate::place::{Boundness, Place};
|
||||
use crate::types::diagnostic::{
|
||||
CALL_NON_CALLABLE, CONFLICTING_ARGUMENT_FORMS, INVALID_ARGUMENT_TYPE, MISSING_ARGUMENT,
|
||||
NO_MATCHING_OVERLOAD, PARAMETER_ALREADY_ASSIGNED, TOO_MANY_POSITIONAL_ARGUMENTS,
|
||||
|
@ -770,7 +770,7 @@ impl<'db> Bindings<'db> {
|
|||
// TODO: we could emit a diagnostic here (if default is not set)
|
||||
overload.set_return_type(
|
||||
match instance_ty.static_member(db, attr_name.value(db)) {
|
||||
Symbol::Type(ty, Boundness::Bound) => {
|
||||
Place::Type(ty, Boundness::Bound) => {
|
||||
if instance_ty.is_fully_static(db) {
|
||||
ty
|
||||
} else {
|
||||
|
@ -782,10 +782,10 @@ impl<'db> Bindings<'db> {
|
|||
union_with_default(ty)
|
||||
}
|
||||
}
|
||||
Symbol::Type(ty, Boundness::PossiblyUnbound) => {
|
||||
Place::Type(ty, Boundness::PossiblyUnbound) => {
|
||||
union_with_default(ty)
|
||||
}
|
||||
Symbol::Unbound => default,
|
||||
Place::Unbound => default,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use super::{
|
|||
infer_unpack_types,
|
||||
};
|
||||
use crate::semantic_index::DeclarationWithConstraint;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::definition::{Definition, DefinitionState};
|
||||
use crate::types::function::{DataclassTransformerParams, KnownFunction};
|
||||
use crate::types::generics::{GenericContext, Specialization};
|
||||
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
|
||||
|
@ -17,17 +17,16 @@ use crate::types::{
|
|||
use crate::{
|
||||
Db, FxOrderSet, KnownModule, Program,
|
||||
module_resolver::file_to_module,
|
||||
place::{
|
||||
Boundness, LookupError, LookupResult, Place, PlaceAndQualifiers, class_symbol,
|
||||
known_module_symbol, place_from_bindings, place_from_declarations,
|
||||
},
|
||||
semantic_index::{
|
||||
ast_ids::HasScopedExpressionId,
|
||||
attribute_assignments,
|
||||
definition::{DefinitionKind, TargetKind},
|
||||
semantic_index,
|
||||
symbol::ScopeId,
|
||||
symbol_table, use_def_map,
|
||||
},
|
||||
symbol::{
|
||||
Boundness, LookupError, LookupResult, Symbol, SymbolAndQualifiers, class_symbol,
|
||||
known_module_symbol, symbol_from_bindings, symbol_from_declarations,
|
||||
place::ScopeId,
|
||||
place_table, semantic_index, use_def_map,
|
||||
},
|
||||
types::{
|
||||
CallArgumentTypes, CallError, CallErrorKind, DynamicType, MetaclassCandidate, TupleType,
|
||||
|
@ -454,7 +453,7 @@ impl<'db> ClassType<'db> {
|
|||
db: &'db dyn Db,
|
||||
name: &str,
|
||||
policy: MemberLookupPolicy,
|
||||
) -> SymbolAndQualifiers<'db> {
|
||||
) -> PlaceAndQualifiers<'db> {
|
||||
let (class_literal, specialization) = self.class_literal(db);
|
||||
class_literal.class_member_inner(db, specialization, name, policy)
|
||||
}
|
||||
|
@ -462,10 +461,10 @@ impl<'db> ClassType<'db> {
|
|||
/// Returns the inferred type of the class member named `name`. Only bound members
|
||||
/// or those marked as ClassVars are considered.
|
||||
///
|
||||
/// Returns [`Symbol::Unbound`] if `name` cannot be found in this class's scope
|
||||
/// Returns [`Place::Unbound`] if `name` cannot be found in this class's scope
|
||||
/// directly. Use [`ClassType::class_member`] if you require a method that will
|
||||
/// traverse through the MRO until it finds the member.
|
||||
pub(super) fn own_class_member(self, db: &'db dyn Db, name: &str) -> SymbolAndQualifiers<'db> {
|
||||
pub(super) fn own_class_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> {
|
||||
let (class_literal, specialization) = self.class_literal(db);
|
||||
class_literal
|
||||
.own_class_member(db, specialization, name)
|
||||
|
@ -475,7 +474,7 @@ impl<'db> ClassType<'db> {
|
|||
/// Look up an instance attribute (available in `__dict__`) of the given name.
|
||||
///
|
||||
/// See [`Type::instance_member`] for more details.
|
||||
pub(super) fn instance_member(self, db: &'db dyn Db, name: &str) -> SymbolAndQualifiers<'db> {
|
||||
pub(super) fn instance_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> {
|
||||
let (class_literal, specialization) = self.class_literal(db);
|
||||
class_literal
|
||||
.instance_member(db, specialization, name)
|
||||
|
@ -484,7 +483,7 @@ impl<'db> ClassType<'db> {
|
|||
|
||||
/// A helper function for `instance_member` that looks up the `name` attribute only on
|
||||
/// this class, not on its superclasses.
|
||||
fn own_instance_member(self, db: &'db dyn Db, name: &str) -> SymbolAndQualifiers<'db> {
|
||||
fn own_instance_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> {
|
||||
let (class_literal, specialization) = self.class_literal(db);
|
||||
class_literal
|
||||
.own_instance_member(db, name)
|
||||
|
@ -502,9 +501,9 @@ impl<'db> ClassType<'db> {
|
|||
MemberLookupPolicy::NO_INSTANCE_FALLBACK
|
||||
| MemberLookupPolicy::META_CLASS_NO_TYPE_FALLBACK,
|
||||
)
|
||||
.symbol;
|
||||
.place;
|
||||
|
||||
if let Symbol::Type(Type::BoundMethod(metaclass_dunder_call_function), _) =
|
||||
if let Place::Type(Type::BoundMethod(metaclass_dunder_call_function), _) =
|
||||
metaclass_dunder_call_function_symbol
|
||||
{
|
||||
// TODO: this intentionally diverges from step 1 in
|
||||
|
@ -520,10 +519,10 @@ impl<'db> ClassType<'db> {
|
|||
"__new__".into(),
|
||||
MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK,
|
||||
)
|
||||
.symbol;
|
||||
.place;
|
||||
|
||||
let dunder_new_function =
|
||||
if let Symbol::Type(Type::FunctionLiteral(dunder_new_function), _) =
|
||||
if let Place::Type(Type::FunctionLiteral(dunder_new_function), _) =
|
||||
dunder_new_function_symbol
|
||||
{
|
||||
// Step 3: If the return type of the `__new__` evaluates to a type that is not a subclass of this class,
|
||||
|
@ -562,7 +561,7 @@ impl<'db> ClassType<'db> {
|
|||
MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK
|
||||
| MemberLookupPolicy::META_CLASS_NO_TYPE_FALLBACK,
|
||||
)
|
||||
.symbol;
|
||||
.place;
|
||||
|
||||
let correct_return_type = self_ty.to_instance(db).unwrap_or_else(Type::unknown);
|
||||
|
||||
|
@ -570,7 +569,7 @@ impl<'db> ClassType<'db> {
|
|||
// same parameters as the `__init__` method after it is bound, and with the return type of
|
||||
// the concrete type of `Self`.
|
||||
let synthesized_dunder_init_callable =
|
||||
if let Symbol::Type(Type::FunctionLiteral(dunder_init_function), _) =
|
||||
if let Place::Type(Type::FunctionLiteral(dunder_init_function), _) =
|
||||
dunder_init_function_symbol
|
||||
{
|
||||
let synthesized_signature = |signature: Signature<'db>| {
|
||||
|
@ -612,9 +611,9 @@ impl<'db> ClassType<'db> {
|
|||
"__new__".into(),
|
||||
MemberLookupPolicy::META_CLASS_NO_TYPE_FALLBACK,
|
||||
)
|
||||
.symbol;
|
||||
.place;
|
||||
|
||||
if let Symbol::Type(Type::FunctionLiteral(new_function), _) = new_function_symbol {
|
||||
if let Place::Type(Type::FunctionLiteral(new_function), _) = new_function_symbol {
|
||||
new_function.into_bound_method_type(db, self_ty)
|
||||
} else {
|
||||
// Fallback if no `object.__new__` is found.
|
||||
|
@ -1136,7 +1135,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
db: &'db dyn Db,
|
||||
name: &str,
|
||||
policy: MemberLookupPolicy,
|
||||
) -> SymbolAndQualifiers<'db> {
|
||||
) -> PlaceAndQualifiers<'db> {
|
||||
self.class_member_inner(db, None, name, policy)
|
||||
}
|
||||
|
||||
|
@ -1146,10 +1145,10 @@ impl<'db> ClassLiteral<'db> {
|
|||
specialization: Option<Specialization<'db>>,
|
||||
name: &str,
|
||||
policy: MemberLookupPolicy,
|
||||
) -> SymbolAndQualifiers<'db> {
|
||||
) -> PlaceAndQualifiers<'db> {
|
||||
if name == "__mro__" {
|
||||
let tuple_elements = self.iter_mro(db, specialization).map(Type::from);
|
||||
return Symbol::bound(TupleType::from_elements(db, tuple_elements)).into();
|
||||
return Place::bound(TupleType::from_elements(db, tuple_elements)).into();
|
||||
}
|
||||
|
||||
self.class_member_from_mro(db, name, policy, self.iter_mro(db, specialization))
|
||||
|
@ -1161,7 +1160,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
name: &str,
|
||||
policy: MemberLookupPolicy,
|
||||
mro_iter: impl Iterator<Item = ClassBase<'db>>,
|
||||
) -> SymbolAndQualifiers<'db> {
|
||||
) -> PlaceAndQualifiers<'db> {
|
||||
// If we encounter a dynamic type in this class's MRO, we'll save that dynamic type
|
||||
// in this variable. After we've traversed the MRO, we'll either:
|
||||
// (1) Use that dynamic type as the type for this attribute,
|
||||
|
@ -1208,18 +1207,18 @@ impl<'db> ClassLiteral<'db> {
|
|||
}
|
||||
|
||||
match (
|
||||
SymbolAndQualifiers::from(lookup_result),
|
||||
PlaceAndQualifiers::from(lookup_result),
|
||||
dynamic_type_to_intersect_with,
|
||||
) {
|
||||
(symbol_and_qualifiers, None) => symbol_and_qualifiers,
|
||||
|
||||
(
|
||||
SymbolAndQualifiers {
|
||||
symbol: Symbol::Type(ty, _),
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Type(ty, _),
|
||||
qualifiers,
|
||||
},
|
||||
Some(dynamic_type),
|
||||
) => Symbol::bound(
|
||||
) => Place::bound(
|
||||
IntersectionBuilder::new(db)
|
||||
.add_positive(ty)
|
||||
.add_positive(dynamic_type)
|
||||
|
@ -1228,19 +1227,19 @@ impl<'db> ClassLiteral<'db> {
|
|||
.with_qualifiers(qualifiers),
|
||||
|
||||
(
|
||||
SymbolAndQualifiers {
|
||||
symbol: Symbol::Unbound,
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Unbound,
|
||||
qualifiers,
|
||||
},
|
||||
Some(dynamic_type),
|
||||
) => Symbol::bound(dynamic_type).with_qualifiers(qualifiers),
|
||||
) => Place::bound(dynamic_type).with_qualifiers(qualifiers),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the inferred type of the class member named `name`. Only bound members
|
||||
/// or those marked as ClassVars are considered.
|
||||
///
|
||||
/// Returns [`Symbol::Unbound`] if `name` cannot be found in this class's scope
|
||||
/// Returns [`Place::Unbound`] if `name` cannot be found in this class's scope
|
||||
/// directly. Use [`ClassLiteral::class_member`] if you require a method that will
|
||||
/// traverse through the MRO until it finds the member.
|
||||
pub(super) fn own_class_member(
|
||||
|
@ -1248,10 +1247,10 @@ impl<'db> ClassLiteral<'db> {
|
|||
db: &'db dyn Db,
|
||||
specialization: Option<Specialization<'db>>,
|
||||
name: &str,
|
||||
) -> SymbolAndQualifiers<'db> {
|
||||
) -> PlaceAndQualifiers<'db> {
|
||||
if name == "__dataclass_fields__" && self.dataclass_params(db).is_some() {
|
||||
// Make this class look like a subclass of the `DataClassInstance` protocol
|
||||
return Symbol::bound(KnownClass::Dict.to_specialized_instance(
|
||||
return Place::bound(KnownClass::Dict.to_specialized_instance(
|
||||
db,
|
||||
[
|
||||
KnownClass::Str.to_instance(db),
|
||||
|
@ -1287,10 +1286,10 @@ impl<'db> ClassLiteral<'db> {
|
|||
}
|
||||
});
|
||||
|
||||
if symbol.symbol.is_unbound() {
|
||||
if symbol.place.is_unbound() {
|
||||
if let Some(synthesized_member) = self.own_synthesized_member(db, specialization, name)
|
||||
{
|
||||
return Symbol::bound(synthesized_member).into();
|
||||
return Place::bound(synthesized_member).into();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1322,7 +1321,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
// itself in this case, so we skip the special descriptor handling.
|
||||
if attr_ty.is_fully_static(db) {
|
||||
let dunder_set = attr_ty.class_member(db, "__set__".into());
|
||||
if let Some(dunder_set) = dunder_set.symbol.ignore_possibly_unbound() {
|
||||
if let Some(dunder_set) = dunder_set.place.ignore_possibly_unbound() {
|
||||
// This type of this attribute is a data descriptor. Instead of overwriting the
|
||||
// descriptor attribute, data-classes will (implicitly) call the `__set__` method
|
||||
// of the descriptor. This means that the synthesized `__init__` parameter for
|
||||
|
@ -1428,7 +1427,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
.to_class_literal(db)
|
||||
.into_class_literal()?
|
||||
.own_class_member(db, None, name)
|
||||
.symbol
|
||||
.place
|
||||
.ignore_possibly_unbound()
|
||||
}
|
||||
_ => None,
|
||||
|
@ -1490,10 +1489,10 @@ impl<'db> ClassLiteral<'db> {
|
|||
let mut attributes = FxOrderMap::default();
|
||||
|
||||
let class_body_scope = self.body_scope(db);
|
||||
let table = symbol_table(db, class_body_scope);
|
||||
let table = place_table(db, class_body_scope);
|
||||
|
||||
let use_def = use_def_map(db, class_body_scope);
|
||||
for (symbol_id, declarations) in use_def.all_public_declarations() {
|
||||
for (place_id, declarations) in use_def.all_public_declarations() {
|
||||
// Here, we exclude all declarations that are not annotated assignments. We need this because
|
||||
// things like function definitions and nested classes would otherwise be considered dataclass
|
||||
// fields. The check is too broad in the sense that it also excludes (weird) constructs where
|
||||
|
@ -1504,7 +1503,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
if !declarations
|
||||
.clone()
|
||||
.all(|DeclarationWithConstraint { declaration, .. }| {
|
||||
declaration.is_some_and(|declaration| {
|
||||
declaration.is_defined_and(|declaration| {
|
||||
matches!(
|
||||
declaration.kind(db),
|
||||
DefinitionKind::AnnotatedAssignment(..)
|
||||
|
@ -1515,18 +1514,18 @@ impl<'db> ClassLiteral<'db> {
|
|||
continue;
|
||||
}
|
||||
|
||||
let symbol = table.symbol(symbol_id);
|
||||
let place_expr = table.place_expr(place_id);
|
||||
|
||||
if let Ok(attr) = symbol_from_declarations(db, declarations) {
|
||||
if let Ok(attr) = place_from_declarations(db, declarations) {
|
||||
if attr.is_class_var() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(attr_ty) = attr.symbol.ignore_possibly_unbound() {
|
||||
let bindings = use_def.public_bindings(symbol_id);
|
||||
let default_ty = symbol_from_bindings(db, bindings).ignore_possibly_unbound();
|
||||
if let Some(attr_ty) = attr.place.ignore_possibly_unbound() {
|
||||
let bindings = use_def.public_bindings(place_id);
|
||||
let default_ty = place_from_bindings(db, bindings).ignore_possibly_unbound();
|
||||
|
||||
attributes.insert(symbol.name().clone(), (attr_ty, default_ty));
|
||||
attributes.insert(place_expr.expect_name().clone(), (attr_ty, default_ty));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1542,7 +1541,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
db: &'db dyn Db,
|
||||
specialization: Option<Specialization<'db>>,
|
||||
name: &str,
|
||||
) -> SymbolAndQualifiers<'db> {
|
||||
) -> PlaceAndQualifiers<'db> {
|
||||
let mut union = UnionBuilder::new(db);
|
||||
let mut union_qualifiers = TypeQualifiers::empty();
|
||||
|
||||
|
@ -1552,13 +1551,13 @@ impl<'db> ClassLiteral<'db> {
|
|||
// Skip over these very special class bases that aren't really classes.
|
||||
}
|
||||
ClassBase::Dynamic(_) => {
|
||||
return SymbolAndQualifiers::todo(
|
||||
return PlaceAndQualifiers::todo(
|
||||
"instance attribute on class with dynamic base",
|
||||
);
|
||||
}
|
||||
ClassBase::Class(class) => {
|
||||
if let member @ SymbolAndQualifiers {
|
||||
symbol: Symbol::Type(ty, boundness),
|
||||
if let member @ PlaceAndQualifiers {
|
||||
place: Place::Type(ty, boundness),
|
||||
qualifiers,
|
||||
} = class.own_instance_member(db, name)
|
||||
{
|
||||
|
@ -1571,7 +1570,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
return member;
|
||||
}
|
||||
|
||||
return Symbol::bound(union.add(ty).build())
|
||||
return Place::bound(union.add(ty).build())
|
||||
.with_qualifiers(union_qualifiers);
|
||||
}
|
||||
|
||||
|
@ -1584,13 +1583,12 @@ impl<'db> ClassLiteral<'db> {
|
|||
}
|
||||
|
||||
if union.is_empty() {
|
||||
Symbol::Unbound.with_qualifiers(TypeQualifiers::empty())
|
||||
Place::Unbound.with_qualifiers(TypeQualifiers::empty())
|
||||
} else {
|
||||
// If we have reached this point, we know that we have only seen possibly-unbound symbols.
|
||||
// If we have reached this point, we know that we have only seen possibly-unbound places.
|
||||
// This means that the final result is still possibly-unbound.
|
||||
|
||||
Symbol::Type(union.build(), Boundness::PossiblyUnbound)
|
||||
.with_qualifiers(union_qualifiers)
|
||||
Place::Type(union.build(), Boundness::PossiblyUnbound).with_qualifiers(union_qualifiers)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1600,7 +1598,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
db: &'db dyn Db,
|
||||
class_body_scope: ScopeId<'db>,
|
||||
name: &str,
|
||||
) -> Symbol<'db> {
|
||||
) -> Place<'db> {
|
||||
// If we do not see any declarations of an attribute, neither in the class body nor in
|
||||
// any method, we build a union of `Unknown` with the inferred types of all bindings of
|
||||
// that attribute. We include `Unknown` in that union to account for the fact that the
|
||||
|
@ -1612,7 +1610,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
let file = class_body_scope.file(db);
|
||||
let index = semantic_index(db, file);
|
||||
let class_map = use_def_map(db, class_body_scope);
|
||||
let class_table = symbol_table(db, class_body_scope);
|
||||
let class_table = place_table(db, class_body_scope);
|
||||
|
||||
for (attribute_assignments, method_scope_id) in
|
||||
attribute_assignments(db, class_body_scope, name)
|
||||
|
@ -1623,11 +1621,11 @@ impl<'db> ClassLiteral<'db> {
|
|||
// The attribute assignment inherits the visibility of the method which contains it
|
||||
let is_method_visible = if let Some(method_def) = method_scope.node(db).as_function() {
|
||||
let method = index.expect_single_definition(method_def);
|
||||
let method_symbol = class_table.symbol_id_by_name(&method_def.name).unwrap();
|
||||
let method_place = class_table.place_id_by_name(&method_def.name).unwrap();
|
||||
class_map
|
||||
.public_bindings(method_symbol)
|
||||
.public_bindings(method_place)
|
||||
.find_map(|bind| {
|
||||
(bind.binding == Some(method))
|
||||
(bind.binding.is_defined_and(|def| def == method))
|
||||
.then(|| class_map.is_binding_visible(db, &bind))
|
||||
})
|
||||
.unwrap_or(Truthiness::AlwaysFalse)
|
||||
|
@ -1642,7 +1640,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
let unbound_visibility = attribute_assignments
|
||||
.peek()
|
||||
.map(|attribute_assignment| {
|
||||
if attribute_assignment.binding.is_none() {
|
||||
if attribute_assignment.binding.is_undefined() {
|
||||
method_map.is_binding_visible(db, attribute_assignment)
|
||||
} else {
|
||||
Truthiness::AlwaysFalse
|
||||
|
@ -1651,7 +1649,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
.unwrap_or(Truthiness::AlwaysFalse);
|
||||
|
||||
for attribute_assignment in attribute_assignments {
|
||||
let Some(binding) = attribute_assignment.binding else {
|
||||
let DefinitionState::Defined(binding) = attribute_assignment.binding else {
|
||||
continue;
|
||||
};
|
||||
match method_map
|
||||
|
@ -1696,10 +1694,10 @@ impl<'db> ClassLiteral<'db> {
|
|||
// TODO: check if there are conflicting declarations
|
||||
match is_attribute_bound {
|
||||
Truthiness::AlwaysTrue => {
|
||||
return Symbol::bound(annotation_ty);
|
||||
return Place::bound(annotation_ty);
|
||||
}
|
||||
Truthiness::Ambiguous => {
|
||||
return Symbol::possibly_unbound(annotation_ty);
|
||||
return Place::possibly_unbound(annotation_ty);
|
||||
}
|
||||
Truthiness::AlwaysFalse => unreachable!(
|
||||
"If the attribute assignments are all invisible, inference of their types should be skipped"
|
||||
|
@ -1722,7 +1720,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
|
||||
union_of_inferred_types = union_of_inferred_types.add(inferred_ty);
|
||||
}
|
||||
TargetKind::NameOrAttribute => {
|
||||
TargetKind::Single => {
|
||||
// We found an un-annotated attribute assignment of the form:
|
||||
//
|
||||
// self.name = <value>
|
||||
|
@ -1748,7 +1746,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
|
||||
union_of_inferred_types = union_of_inferred_types.add(inferred_ty);
|
||||
}
|
||||
TargetKind::NameOrAttribute => {
|
||||
TargetKind::Single => {
|
||||
// We found an attribute assignment like:
|
||||
//
|
||||
// for self.name in <iterable>:
|
||||
|
@ -1778,7 +1776,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
|
||||
union_of_inferred_types = union_of_inferred_types.add(inferred_ty);
|
||||
}
|
||||
TargetKind::NameOrAttribute => {
|
||||
TargetKind::Single => {
|
||||
// We found an attribute assignment like:
|
||||
//
|
||||
// with <context_manager> as self.name:
|
||||
|
@ -1808,7 +1806,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
|
||||
union_of_inferred_types = union_of_inferred_types.add(inferred_ty);
|
||||
}
|
||||
TargetKind::NameOrAttribute => {
|
||||
TargetKind::Single => {
|
||||
// We found an attribute assignment like:
|
||||
//
|
||||
// [... for self.name in <iterable>]
|
||||
|
@ -1836,42 +1834,42 @@ impl<'db> ClassLiteral<'db> {
|
|||
}
|
||||
|
||||
match is_attribute_bound {
|
||||
Truthiness::AlwaysTrue => Symbol::bound(union_of_inferred_types.build()),
|
||||
Truthiness::Ambiguous => Symbol::possibly_unbound(union_of_inferred_types.build()),
|
||||
Truthiness::AlwaysFalse => Symbol::Unbound,
|
||||
Truthiness::AlwaysTrue => Place::bound(union_of_inferred_types.build()),
|
||||
Truthiness::Ambiguous => Place::possibly_unbound(union_of_inferred_types.build()),
|
||||
Truthiness::AlwaysFalse => Place::Unbound,
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper function for `instance_member` that looks up the `name` attribute only on
|
||||
/// this class, not on its superclasses.
|
||||
fn own_instance_member(self, db: &'db dyn Db, name: &str) -> SymbolAndQualifiers<'db> {
|
||||
fn own_instance_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> {
|
||||
// TODO: There are many things that are not yet implemented here:
|
||||
// - `typing.Final`
|
||||
// - Proper diagnostics
|
||||
|
||||
let body_scope = self.body_scope(db);
|
||||
let table = symbol_table(db, body_scope);
|
||||
let table = place_table(db, body_scope);
|
||||
|
||||
if let Some(symbol_id) = table.symbol_id_by_name(name) {
|
||||
if let Some(place_id) = table.place_id_by_name(name) {
|
||||
let use_def = use_def_map(db, body_scope);
|
||||
|
||||
let declarations = use_def.public_declarations(symbol_id);
|
||||
let declared_and_qualifiers = symbol_from_declarations(db, declarations);
|
||||
let declarations = use_def.public_declarations(place_id);
|
||||
let declared_and_qualifiers = place_from_declarations(db, declarations);
|
||||
match declared_and_qualifiers {
|
||||
Ok(SymbolAndQualifiers {
|
||||
symbol: mut declared @ Symbol::Type(declared_ty, declaredness),
|
||||
Ok(PlaceAndQualifiers {
|
||||
place: mut declared @ Place::Type(declared_ty, declaredness),
|
||||
qualifiers,
|
||||
}) => {
|
||||
// For the purpose of finding instance attributes, ignore `ClassVar`
|
||||
// declarations:
|
||||
if qualifiers.contains(TypeQualifiers::CLASS_VAR) {
|
||||
declared = Symbol::Unbound;
|
||||
declared = Place::Unbound;
|
||||
}
|
||||
|
||||
// The attribute is declared in the class body.
|
||||
|
||||
let bindings = use_def.public_bindings(symbol_id);
|
||||
let inferred = symbol_from_bindings(db, bindings);
|
||||
let bindings = use_def.public_bindings(place_id);
|
||||
let inferred = place_from_bindings(db, bindings);
|
||||
let has_binding = !inferred.is_unbound();
|
||||
|
||||
if has_binding {
|
||||
|
@ -1887,7 +1885,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
// we trust the declared type.
|
||||
declared.with_qualifiers(qualifiers)
|
||||
} else {
|
||||
Symbol::Type(
|
||||
Place::Type(
|
||||
UnionType::from_elements(db, [declared_ty, implicit_ty]),
|
||||
declaredness,
|
||||
)
|
||||
|
@ -1900,7 +1898,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
// has a class-level default value, but it would not be
|
||||
// found in a `__dict__` lookup.
|
||||
|
||||
Symbol::Unbound.into()
|
||||
Place::Unbound.into()
|
||||
}
|
||||
} else {
|
||||
// The attribute is declared but not bound in the class body.
|
||||
|
@ -1916,7 +1914,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
Self::implicit_instance_attribute(db, body_scope, name)
|
||||
.ignore_possibly_unbound()
|
||||
{
|
||||
Symbol::Type(
|
||||
Place::Type(
|
||||
UnionType::from_elements(db, [declared_ty, implicit_ty]),
|
||||
declaredness,
|
||||
)
|
||||
|
@ -1928,8 +1926,8 @@ impl<'db> ClassLiteral<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
Ok(SymbolAndQualifiers {
|
||||
symbol: Symbol::Unbound,
|
||||
Ok(PlaceAndQualifiers {
|
||||
place: Place::Unbound,
|
||||
qualifiers: _,
|
||||
}) => {
|
||||
// The attribute is not *declared* in the class body. It could still be declared/bound
|
||||
|
@ -1939,7 +1937,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
}
|
||||
Err((declared, _conflicting_declarations)) => {
|
||||
// There are conflicting declarations for this attribute in the class body.
|
||||
Symbol::bound(declared.inner_type()).with_qualifiers(declared.qualifiers())
|
||||
Place::bound(declared.inner_type()).with_qualifiers(declared.qualifiers())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -2454,16 +2452,16 @@ impl<'db> KnownClass {
|
|||
self,
|
||||
db: &'db dyn Db,
|
||||
) -> Result<ClassLiteral<'db>, KnownClassLookupError<'db>> {
|
||||
let symbol = known_module_symbol(db, self.canonical_module(db), self.name(db)).symbol;
|
||||
let symbol = known_module_symbol(db, self.canonical_module(db), self.name(db)).place;
|
||||
match symbol {
|
||||
Symbol::Type(Type::ClassLiteral(class_literal), Boundness::Bound) => Ok(class_literal),
|
||||
Symbol::Type(Type::ClassLiteral(class_literal), Boundness::PossiblyUnbound) => {
|
||||
Place::Type(Type::ClassLiteral(class_literal), Boundness::Bound) => Ok(class_literal),
|
||||
Place::Type(Type::ClassLiteral(class_literal), Boundness::PossiblyUnbound) => {
|
||||
Err(KnownClassLookupError::ClassPossiblyUnbound { class_literal })
|
||||
}
|
||||
Symbol::Type(found_type, _) => {
|
||||
Place::Type(found_type, _) => {
|
||||
Err(KnownClassLookupError::SymbolNotAClass { found_type })
|
||||
}
|
||||
Symbol::Unbound => Err(KnownClassLookupError::ClassNotFound),
|
||||
Place::Unbound => Err(KnownClassLookupError::ClassNotFound),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,8 +11,8 @@ use ruff_text_size::{Ranged, TextRange};
|
|||
use super::{Type, TypeCheckDiagnostics, binding_type};
|
||||
|
||||
use crate::lint::LintSource;
|
||||
use crate::semantic_index::place::ScopeId;
|
||||
use crate::semantic_index::semantic_index;
|
||||
use crate::semantic_index::symbol::ScopeId;
|
||||
use crate::types::function::FunctionDecorators;
|
||||
use crate::{
|
||||
Db,
|
||||
|
|
|
@ -794,7 +794,7 @@ mod tests {
|
|||
|
||||
use crate::Db;
|
||||
use crate::db::tests::setup_db;
|
||||
use crate::symbol::typing_extensions_symbol;
|
||||
use crate::place::typing_extensions_symbol;
|
||||
use crate::types::{KnownClass, Parameter, Parameters, Signature, StringLiteralType, Type};
|
||||
|
||||
#[test]
|
||||
|
@ -833,7 +833,7 @@ mod tests {
|
|||
);
|
||||
|
||||
let iterator_synthesized = typing_extensions_symbol(&db, "Iterator")
|
||||
.symbol
|
||||
.place
|
||||
.ignore_possibly_unbound()
|
||||
.unwrap()
|
||||
.to_instance(&db)
|
||||
|
|
|
@ -58,11 +58,11 @@ use ruff_python_ast as ast;
|
|||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::module_resolver::{KnownModule, file_to_module};
|
||||
use crate::place::{Boundness, Place, place_from_bindings};
|
||||
use crate::semantic_index::ast_ids::HasScopedUseId;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::place::ScopeId;
|
||||
use crate::semantic_index::semantic_index;
|
||||
use crate::semantic_index::symbol::ScopeId;
|
||||
use crate::symbol::{Boundness, Symbol, symbol_from_bindings};
|
||||
use crate::types::generics::GenericContext;
|
||||
use crate::types::narrow::ClassInfoConstraintFunction;
|
||||
use crate::types::signatures::{CallableSignature, Signature};
|
||||
|
@ -234,8 +234,8 @@ impl<'db> OverloadLiteral<'db> {
|
|||
.name
|
||||
.scoped_use_id(db, scope);
|
||||
|
||||
let Symbol::Type(Type::FunctionLiteral(previous_type), Boundness::Bound) =
|
||||
symbol_from_bindings(db, use_def.bindings_at_use(use_id))
|
||||
let Place::Type(Type::FunctionLiteral(previous_type), Boundness::Bound) =
|
||||
place_from_bindings(db, use_def.bindings_at_use(use_id))
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
@ -927,7 +927,7 @@ pub(crate) mod tests {
|
|||
|
||||
use super::*;
|
||||
use crate::db::tests::setup_db;
|
||||
use crate::symbol::known_module_symbol;
|
||||
use crate::place::known_module_symbol;
|
||||
|
||||
#[test]
|
||||
fn known_function_roundtrip_from_str() {
|
||||
|
@ -977,7 +977,7 @@ pub(crate) mod tests {
|
|||
};
|
||||
|
||||
let function_definition = known_module_symbol(&db, module, function_name)
|
||||
.symbol
|
||||
.place
|
||||
.expect_type()
|
||||
.expect_function_literal()
|
||||
.definition(&db);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use crate::Db;
|
||||
use crate::semantic_index::symbol::ScopeId;
|
||||
use crate::place::{imported_symbol, place_from_bindings, place_from_declarations};
|
||||
use crate::semantic_index::place::ScopeId;
|
||||
use crate::semantic_index::{
|
||||
attribute_scopes, global_scope, semantic_index, symbol_table, use_def_map,
|
||||
attribute_scopes, global_scope, place_table, semantic_index, use_def_map,
|
||||
};
|
||||
use crate::symbol::{imported_symbol, symbol_from_bindings, symbol_from_declarations};
|
||||
use crate::types::{ClassBase, ClassLiteral, KnownClass, Type};
|
||||
use ruff_python_ast::name::Name;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
@ -13,28 +13,27 @@ pub(crate) fn all_declarations_and_bindings<'db>(
|
|||
scope_id: ScopeId<'db>,
|
||||
) -> impl Iterator<Item = Name> + 'db {
|
||||
let use_def_map = use_def_map(db, scope_id);
|
||||
let symbol_table = symbol_table(db, scope_id);
|
||||
let table = place_table(db, scope_id);
|
||||
|
||||
use_def_map
|
||||
.all_public_declarations()
|
||||
.filter_map(move |(symbol_id, declarations)| {
|
||||
if symbol_from_declarations(db, declarations)
|
||||
.is_ok_and(|result| !result.symbol.is_unbound())
|
||||
{
|
||||
Some(symbol_table.symbol(symbol_id).name().clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
place_from_declarations(db, declarations)
|
||||
.ok()
|
||||
.and_then(|result| {
|
||||
result
|
||||
.place
|
||||
.ignore_possibly_unbound()
|
||||
.and_then(|_| table.place_expr(symbol_id).as_name().cloned())
|
||||
})
|
||||
})
|
||||
.chain(
|
||||
use_def_map
|
||||
.all_public_bindings()
|
||||
.filter_map(move |(symbol_id, bindings)| {
|
||||
if symbol_from_bindings(db, bindings).is_unbound() {
|
||||
None
|
||||
} else {
|
||||
Some(symbol_table.symbol(symbol_id).name().clone())
|
||||
}
|
||||
place_from_bindings(db, bindings)
|
||||
.ignore_possibly_unbound()
|
||||
.and_then(|_| table.place_expr(symbol_id).as_name().cloned())
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
@ -132,16 +131,18 @@ impl AllMembers {
|
|||
|
||||
let module_scope = global_scope(db, file);
|
||||
let use_def_map = use_def_map(db, module_scope);
|
||||
let symbol_table = symbol_table(db, module_scope);
|
||||
let place_table = place_table(db, module_scope);
|
||||
|
||||
for (symbol_id, _) in use_def_map.all_public_declarations() {
|
||||
let symbol_name = symbol_table.symbol(symbol_id).name();
|
||||
let Some(symbol_name) = place_table.place_expr(symbol_id).as_name() else {
|
||||
continue;
|
||||
};
|
||||
if !imported_symbol(db, file, symbol_name, None)
|
||||
.symbol
|
||||
.place
|
||||
.is_unbound()
|
||||
{
|
||||
self.members
|
||||
.insert(symbol_table.symbol(symbol_id).name().clone());
|
||||
.insert(place_table.place_expr(symbol_id).expect_name().clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -182,9 +183,10 @@ impl AllMembers {
|
|||
let file = class_body_scope.file(db);
|
||||
let index = semantic_index(db, file);
|
||||
for function_scope_id in attribute_scopes(db, class_body_scope) {
|
||||
let attribute_table = index.instance_attribute_table(function_scope_id);
|
||||
for symbol in attribute_table.symbols() {
|
||||
self.members.insert(symbol.name().clone());
|
||||
let place_table = index.place_table(function_scope_id);
|
||||
for instance_attribute in place_table.instance_attributes() {
|
||||
let name = instance_attribute.sub_segments()[0].as_member().unwrap();
|
||||
self.members.insert(name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -4,7 +4,7 @@ use std::marker::PhantomData;
|
|||
|
||||
use super::protocol_class::ProtocolInterface;
|
||||
use super::{ClassType, KnownClass, SubclassOfType, Type};
|
||||
use crate::symbol::{Boundness, Symbol, SymbolAndQualifiers};
|
||||
use crate::place::{Boundness, Place, PlaceAndQualifiers};
|
||||
use crate::types::{ClassLiteral, TypeMapping, TypeVarInstance};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
|
@ -47,8 +47,8 @@ impl<'db> Type<'db> {
|
|||
// TODO: this should consider the types of the protocol members
|
||||
protocol.inner.interface(db).members(db).all(|member| {
|
||||
matches!(
|
||||
self.member(db, member.name()).symbol,
|
||||
Symbol::Type(_, Boundness::Bound)
|
||||
self.member(db, member.name()).place,
|
||||
Place::Type(_, Boundness::Bound)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
@ -294,14 +294,14 @@ impl<'db> ProtocolInstanceType<'db> {
|
|||
false
|
||||
}
|
||||
|
||||
pub(crate) fn instance_member(self, db: &'db dyn Db, name: &str) -> SymbolAndQualifiers<'db> {
|
||||
pub(crate) fn instance_member(self, db: &'db dyn Db, name: &str) -> PlaceAndQualifiers<'db> {
|
||||
match self.inner {
|
||||
Protocol::FromClass(class) => class.instance_member(db, name),
|
||||
Protocol::Synthesized(synthesized) => synthesized
|
||||
.interface()
|
||||
.member_by_name(db, name)
|
||||
.map(|member| SymbolAndQualifiers {
|
||||
symbol: Symbol::bound(member.ty()),
|
||||
.map(|member| PlaceAndQualifiers {
|
||||
place: Place::bound(member.ty()),
|
||||
qualifiers: member.qualifiers(),
|
||||
})
|
||||
.unwrap_or_else(|| KnownClass::Object.to_instance(db).instance_member(db, name)),
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use crate::Db;
|
||||
use crate::semantic_index::ast_ids::HasScopedExpressionId;
|
||||
use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::place::{PlaceTable, ScopeId, ScopedPlaceId};
|
||||
use crate::semantic_index::place_table;
|
||||
use crate::semantic_index::predicate::{
|
||||
PatternPredicate, PatternPredicateKind, Predicate, PredicateNode,
|
||||
};
|
||||
use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId, SymbolTable};
|
||||
use crate::semantic_index::symbol_table;
|
||||
use crate::types::function::KnownFunction;
|
||||
use crate::types::infer::infer_same_file_expression_type;
|
||||
use crate::types::{
|
||||
|
@ -42,7 +42,7 @@ use super::UnionType;
|
|||
pub(crate) fn infer_narrowing_constraint<'db>(
|
||||
db: &'db dyn Db,
|
||||
predicate: Predicate<'db>,
|
||||
symbol: ScopedSymbolId,
|
||||
place: ScopedPlaceId,
|
||||
) -> Option<Type<'db>> {
|
||||
let constraints = match predicate.node {
|
||||
PredicateNode::Expression(expression) => {
|
||||
|
@ -62,7 +62,7 @@ pub(crate) fn infer_narrowing_constraint<'db>(
|
|||
PredicateNode::StarImportPlaceholder(_) => return None,
|
||||
};
|
||||
if let Some(constraints) = constraints {
|
||||
constraints.get(&symbol).copied()
|
||||
constraints.get(&place).copied()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -190,7 +190,7 @@ impl ClassInfoConstraintFunction {
|
|||
}
|
||||
}
|
||||
|
||||
type NarrowingConstraints<'db> = FxHashMap<ScopedSymbolId, Type<'db>>;
|
||||
type NarrowingConstraints<'db> = FxHashMap<ScopedPlaceId, Type<'db>>;
|
||||
|
||||
fn merge_constraints_and<'db>(
|
||||
into: &mut NarrowingConstraints<'db>,
|
||||
|
@ -235,7 +235,7 @@ fn merge_constraints_or<'db>(
|
|||
}
|
||||
|
||||
fn negate_if<'db>(constraints: &mut NarrowingConstraints<'db>, db: &'db dyn Db, yes: bool) {
|
||||
for (_symbol, ty) in constraints.iter_mut() {
|
||||
for (_place, ty) in constraints.iter_mut() {
|
||||
*ty = ty.negate_if(db, yes);
|
||||
}
|
||||
}
|
||||
|
@ -347,8 +347,8 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
|
|||
})
|
||||
}
|
||||
|
||||
fn symbols(&self) -> &'db SymbolTable {
|
||||
symbol_table(self.db, self.scope())
|
||||
fn places(&self) -> &'db PlaceTable {
|
||||
place_table(self.db, self.scope())
|
||||
}
|
||||
|
||||
fn scope(&self) -> ScopeId<'db> {
|
||||
|
@ -360,9 +360,9 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
|
|||
}
|
||||
|
||||
#[track_caller]
|
||||
fn expect_expr_name_symbol(&self, symbol: &str) -> ScopedSymbolId {
|
||||
self.symbols()
|
||||
.symbol_id_by_name(symbol)
|
||||
fn expect_expr_name_symbol(&self, symbol: &str) -> ScopedPlaceId {
|
||||
self.places()
|
||||
.place_id_by_name(symbol)
|
||||
.expect("We should always have a symbol for every `Name` node")
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::db::tests::TestDb;
|
||||
use crate::symbol::{builtins_symbol, known_module_symbol};
|
||||
use crate::place::{builtins_symbol, known_module_symbol};
|
||||
use crate::types::{
|
||||
BoundMethodType, CallableType, IntersectionBuilder, KnownClass, Parameter, Parameters,
|
||||
Signature, SpecialFormType, SubclassOfType, TupleType, Type, UnionType,
|
||||
|
@ -130,20 +130,20 @@ impl Ty {
|
|||
Ty::LiteralString => Type::LiteralString,
|
||||
Ty::BytesLiteral(s) => Type::bytes_literal(db, s.as_bytes()),
|
||||
Ty::BuiltinInstance(s) => builtins_symbol(db, s)
|
||||
.symbol
|
||||
.place
|
||||
.expect_type()
|
||||
.to_instance(db)
|
||||
.unwrap(),
|
||||
Ty::AbcInstance(s) => known_module_symbol(db, KnownModule::Abc, s)
|
||||
.symbol
|
||||
.place
|
||||
.expect_type()
|
||||
.to_instance(db)
|
||||
.unwrap(),
|
||||
Ty::AbcClassLiteral(s) => known_module_symbol(db, KnownModule::Abc, s)
|
||||
.symbol
|
||||
.place
|
||||
.expect_type(),
|
||||
Ty::TypingLiteral => Type::SpecialForm(SpecialFormType::Literal),
|
||||
Ty::BuiltinClassLiteral(s) => builtins_symbol(db, s).symbol.expect_type(),
|
||||
Ty::BuiltinClassLiteral(s) => builtins_symbol(db, s).place.expect_type(),
|
||||
Ty::KnownClassInstance(known_class) => known_class.to_instance(db),
|
||||
Ty::Union(tys) => {
|
||||
UnionType::from_elements(db, tys.into_iter().map(|ty| ty.into_type(db)))
|
||||
|
@ -166,7 +166,7 @@ impl Ty {
|
|||
Ty::SubclassOfBuiltinClass(s) => SubclassOfType::from(
|
||||
db,
|
||||
builtins_symbol(db, s)
|
||||
.symbol
|
||||
.place
|
||||
.expect_type()
|
||||
.expect_class_literal()
|
||||
.default_specialization(db),
|
||||
|
@ -174,17 +174,17 @@ impl Ty {
|
|||
Ty::SubclassOfAbcClass(s) => SubclassOfType::from(
|
||||
db,
|
||||
known_module_symbol(db, KnownModule::Abc, s)
|
||||
.symbol
|
||||
.place
|
||||
.expect_type()
|
||||
.expect_class_literal()
|
||||
.default_specialization(db),
|
||||
),
|
||||
Ty::AlwaysTruthy => Type::AlwaysTruthy,
|
||||
Ty::AlwaysFalsy => Type::AlwaysFalsy,
|
||||
Ty::BuiltinsFunction(name) => builtins_symbol(db, name).symbol.expect_type(),
|
||||
Ty::BuiltinsFunction(name) => builtins_symbol(db, name).place.expect_type(),
|
||||
Ty::BuiltinsBoundMethod { class, method } => {
|
||||
let builtins_class = builtins_symbol(db, class).symbol.expect_type();
|
||||
let function = builtins_class.member(db, method).symbol.expect_type();
|
||||
let builtins_class = builtins_symbol(db, class).place.expect_type();
|
||||
let function = builtins_class.member(db, method).place.expect_type();
|
||||
|
||||
create_bound_method(db, function, builtins_class)
|
||||
}
|
||||
|
|
|
@ -5,10 +5,11 @@ use itertools::{Either, Itertools};
|
|||
use ruff_python_ast::name::Name;
|
||||
|
||||
use crate::{
|
||||
semantic_index::{symbol_table, use_def_map},
|
||||
symbol::{symbol_from_bindings, symbol_from_declarations},
|
||||
types::function::KnownFunction,
|
||||
types::{ClassBase, ClassLiteral, Type, TypeMapping, TypeQualifiers, TypeVarInstance},
|
||||
place::{place_from_bindings, place_from_declarations},
|
||||
semantic_index::{place_table, use_def_map},
|
||||
types::{
|
||||
ClassBase, ClassLiteral, KnownFunction, Type, TypeMapping, TypeQualifiers, TypeVarInstance,
|
||||
},
|
||||
{Db, FxOrderSet},
|
||||
};
|
||||
|
||||
|
@ -321,19 +322,19 @@ fn cached_protocol_interface<'db>(
|
|||
{
|
||||
let parent_scope = parent_protocol.body_scope(db);
|
||||
let use_def_map = use_def_map(db, parent_scope);
|
||||
let symbol_table = symbol_table(db, parent_scope);
|
||||
let place_table = place_table(db, parent_scope);
|
||||
|
||||
members.extend(
|
||||
use_def_map
|
||||
.all_public_declarations()
|
||||
.flat_map(|(symbol_id, declarations)| {
|
||||
symbol_from_declarations(db, declarations).map(|symbol| (symbol_id, symbol))
|
||||
.flat_map(|(place_id, declarations)| {
|
||||
place_from_declarations(db, declarations).map(|place| (place_id, place))
|
||||
})
|
||||
.filter_map(|(symbol_id, symbol)| {
|
||||
symbol
|
||||
.symbol
|
||||
.filter_map(|(place_id, place)| {
|
||||
place
|
||||
.place
|
||||
.ignore_possibly_unbound()
|
||||
.map(|ty| (symbol_id, ty, symbol.qualifiers))
|
||||
.map(|ty| (place_id, ty, place.qualifiers))
|
||||
})
|
||||
// Bindings in the class body that are not declared in the class body
|
||||
// are not valid protocol members, and we plan to emit diagnostics for them
|
||||
|
@ -346,14 +347,18 @@ fn cached_protocol_interface<'db>(
|
|||
.chain(
|
||||
use_def_map
|
||||
.all_public_bindings()
|
||||
.filter_map(|(symbol_id, bindings)| {
|
||||
symbol_from_bindings(db, bindings)
|
||||
.filter_map(|(place_id, bindings)| {
|
||||
place_from_bindings(db, bindings)
|
||||
.ignore_possibly_unbound()
|
||||
.map(|ty| (symbol_id, ty, TypeQualifiers::default()))
|
||||
.map(|ty| (place_id, ty, TypeQualifiers::default()))
|
||||
}),
|
||||
)
|
||||
.map(|(symbol_id, member, qualifiers)| {
|
||||
(symbol_table.symbol(symbol_id).name(), member, qualifiers)
|
||||
.filter_map(|(place_id, member, qualifiers)| {
|
||||
Some((
|
||||
place_table.place_expr(place_id).as_name()?,
|
||||
member,
|
||||
qualifiers,
|
||||
))
|
||||
})
|
||||
.filter(|(name, _, _)| !excluded_from_proto_members(name))
|
||||
.map(|(name, ty, qualifiers)| {
|
||||
|
|
|
@ -1533,16 +1533,15 @@ pub(crate) enum ParameterForm {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::db::tests::{TestDb, setup_db};
|
||||
use crate::symbol::global_symbol;
|
||||
use crate::types::KnownClass;
|
||||
use crate::types::function::FunctionType;
|
||||
use crate::place::global_symbol;
|
||||
use crate::types::{FunctionType, KnownClass};
|
||||
use ruff_db::system::DbWithWritableSystem as _;
|
||||
|
||||
#[track_caller]
|
||||
fn get_function_f<'db>(db: &'db TestDb, file: &'static str) -> FunctionType<'db> {
|
||||
let module = ruff_db::files::system_path_to_file(db, file).unwrap();
|
||||
global_symbol(db, module, "f")
|
||||
.symbol
|
||||
.place
|
||||
.expect_type()
|
||||
.expect_function_literal()
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use ruff_python_ast as ast;
|
||||
|
||||
use crate::db::Db;
|
||||
use crate::symbol::{Boundness, Symbol};
|
||||
use crate::place::{Boundness, Place};
|
||||
use crate::types::class_base::ClassBase;
|
||||
use crate::types::diagnostic::report_base_with_incompatible_slots;
|
||||
use crate::types::{ClassLiteral, Type};
|
||||
|
@ -24,7 +24,7 @@ enum SlotsKind {
|
|||
|
||||
impl SlotsKind {
|
||||
fn from(db: &dyn Db, base: ClassLiteral) -> Self {
|
||||
let Symbol::Type(slots_ty, bound) = base.own_class_member(db, None, "__slots__").symbol
|
||||
let Place::Type(slots_ty, bound) = base.own_class_member(db, None, "__slots__").place
|
||||
else {
|
||||
return Self::NotSpecified;
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::symbol::SymbolAndQualifiers;
|
||||
use crate::place::PlaceAndQualifiers;
|
||||
use crate::types::{
|
||||
ClassType, DynamicType, KnownClass, MemberLookupPolicy, Type, TypeMapping, TypeVarInstance,
|
||||
};
|
||||
|
@ -99,7 +99,7 @@ impl<'db> SubclassOfType<'db> {
|
|||
db: &'db dyn Db,
|
||||
name: &str,
|
||||
policy: MemberLookupPolicy,
|
||||
) -> Option<SymbolAndQualifiers<'db>> {
|
||||
) -> Option<PlaceAndQualifiers<'db>> {
|
||||
Type::from(self.subclass_of).find_name_in_mro_with_policy(db, name, policy)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ use ruff_python_ast::{self as ast, AnyNodeRef};
|
|||
|
||||
use crate::Db;
|
||||
use crate::semantic_index::ast_ids::{HasScopedExpressionId, ScopedExpressionId};
|
||||
use crate::semantic_index::symbol::ScopeId;
|
||||
use crate::semantic_index::place::ScopeId;
|
||||
use crate::types::{Type, TypeCheckDiagnostics, infer_expression_types};
|
||||
use crate::unpack::{UnpackKind, UnpackValue};
|
||||
|
||||
|
@ -84,7 +84,7 @@ impl<'db> Unpacker<'db> {
|
|||
value_ty: Type<'db>,
|
||||
) {
|
||||
match target {
|
||||
ast::Expr::Name(_) | ast::Expr::Attribute(_) => {
|
||||
ast::Expr::Name(_) | ast::Expr::Attribute(_) | ast::Expr::Subscript(_) => {
|
||||
self.targets.insert(
|
||||
target.scoped_expression_id(self.db(), self.target_scope),
|
||||
value_ty,
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::Db;
|
|||
use crate::ast_node_ref::AstNodeRef;
|
||||
use crate::semantic_index::ast_ids::{HasScopedExpressionId, ScopedExpressionId};
|
||||
use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::symbol::{FileScopeId, ScopeId};
|
||||
use crate::semantic_index::place::{FileScopeId, ScopeId};
|
||||
|
||||
/// This ingredient represents a single unpacking.
|
||||
///
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue