From e8cfb341f2f31d38229f100a8d6e42503a7ded2c Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 27 Nov 2024 14:18:52 +0100 Subject: [PATCH 01/65] [red-knot] Statically known branches --- .../resources/mdtest/annotations/never.md | 19 +- .../mdtest/assignment/annotations.md | 2 - .../resources/mdtest/boolean/short_circuit.md | 7 +- .../mdtest/call/callable_instance.md | 4 +- .../resources/mdtest/expression/if.md | 2 +- .../resources/mdtest/literal/ellipsis.md | 20 +- .../resources/mdtest/narrow/issubclass.md | 10 +- .../mdtest/statically_known_branches.md | 1162 +++++++++++++++++ .../resources/mdtest/subscript/tuple.md | 5 +- .../src/semantic_index.rs | 8 +- .../src/semantic_index/builder.rs | 152 ++- .../src/semantic_index/constraint.rs | 15 +- .../src/semantic_index/use_def.rs | 229 +++- .../src/semantic_index/use_def/bitset.rs | 77 -- .../semantic_index/use_def/symbol_state.rs | 776 +++++++---- .../semantic_index/visibility_constraint.rs | 34 + crates/red_knot_python_semantic/src/types.rs | 293 +++-- .../src/types/infer.rs | 103 +- .../src/types/narrow.rs | 41 +- .../src/types/static_truthiness.rs | 94 ++ 20 files changed, 2387 insertions(+), 666 deletions(-) create mode 100644 crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md create mode 100644 crates/red_knot_python_semantic/src/semantic_index/visibility_constraint.rs create mode 100644 crates/red_knot_python_semantic/src/types/static_truthiness.rs diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/never.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/never.md index f3aeb7d5d6ff4..81efd2d864ed1 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/annotations/never.md +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/never.md @@ -47,7 +47,9 @@ def f(): ## `typing.Never` -`typing.Never` is only available in Python 3.11 and later: +`typing.Never` is only available in Python 3.11 and later. + +### Python 3.11 ```toml [environment] @@ -57,8 +59,17 @@ python-version = "3.11" ```py from typing import Never -x: Never +reveal_type(Never) # revealed: typing.Never +``` -def f(): - reveal_type(x) # revealed: Never +### Python 3.10 + +```toml +[environment] +python-version = "3.10" +``` + +```py +# error: [unresolved-import] +from typing import Never ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/assignment/annotations.md b/crates/red_knot_python_semantic/resources/mdtest/assignment/annotations.md index c3977ed46b6c4..f696cd4ea414f 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/assignment/annotations.md +++ b/crates/red_knot_python_semantic/resources/mdtest/assignment/annotations.md @@ -33,8 +33,6 @@ b: tuple[int] = (42,) c: tuple[str, int] = ("42", 42) d: tuple[tuple[str, str], tuple[int, int]] = (("foo", "foo"), (42, 42)) e: tuple[str, ...] = () -# TODO: we should not emit this error -# error: [call-possibly-unbound-method] "Method `__class_getitem__` of type `Literal[tuple]` is possibly unbound" f: tuple[str, *tuple[int, ...], bytes] = ("42", b"42") g: tuple[str, Unpack[tuple[int, ...]], bytes] = ("42", b"42") h: tuple[list[int], list[int]] = ([], []) diff --git a/crates/red_knot_python_semantic/resources/mdtest/boolean/short_circuit.md b/crates/red_knot_python_semantic/resources/mdtest/boolean/short_circuit.md index 9ee078606d5cd..6ad75f185bb35 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/boolean/short_circuit.md +++ b/crates/red_knot_python_semantic/resources/mdtest/boolean/short_circuit.md @@ -32,13 +32,10 @@ def _(flag: bool): ```py if True or (x := 1): - # TODO: infer that the second arm is never executed, and raise `unresolved-reference`. - # error: [possibly-unresolved-reference] - reveal_type(x) # revealed: Literal[1] + # error: [unresolved-reference] + reveal_type(x) # revealed: Unknown if True and (x := 1): - # TODO: infer that the second arm is always executed, do not raise a diagnostic - # error: [possibly-unresolved-reference] reveal_type(x) # revealed: Literal[1] ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/call/callable_instance.md b/crates/red_knot_python_semantic/resources/mdtest/call/callable_instance.md index 635082de5dfa2..746aee725f547 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/call/callable_instance.md +++ b/crates/red_knot_python_semantic/resources/mdtest/call/callable_instance.md @@ -67,6 +67,6 @@ def _(flag: bool): def __call__(self) -> int: ... a = NonCallable() - # error: "Object of type `Literal[__call__] | Literal[1]` is not callable (due to union element `Literal[1]`)" - reveal_type(a()) # revealed: int | Unknown + # error: "Object of type `Literal[1] | Literal[__call__]` is not callable (due to union element `Literal[1]`)" + reveal_type(a()) # revealed: Unknown | int ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/expression/if.md b/crates/red_knot_python_semantic/resources/mdtest/expression/if.md index 79faa45426855..6461522cefa46 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/expression/if.md +++ b/crates/red_knot_python_semantic/resources/mdtest/expression/if.md @@ -7,7 +7,7 @@ def _(flag: bool): reveal_type(1 if flag else 2) # revealed: Literal[1, 2] ``` -## Statically known branches +## Statically known conditions in if-expressions ```py reveal_type(1 if True else 2) # revealed: Literal[1] diff --git a/crates/red_knot_python_semantic/resources/mdtest/literal/ellipsis.md b/crates/red_knot_python_semantic/resources/mdtest/literal/ellipsis.md index 2b7bb7c61d9b1..241b498372d49 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/literal/ellipsis.md +++ b/crates/red_knot_python_semantic/resources/mdtest/literal/ellipsis.md @@ -1,7 +1,23 @@ # Ellipsis literals -## Simple +## Python 3.9 + +```toml +[environment] +python-version = "3.9" +``` + +```py +reveal_type(...) # revealed: ellipsis +``` + +## Python 3.10 + +```toml +[environment] +python-version = "3.10" +``` ```py -reveal_type(...) # revealed: EllipsisType | ellipsis +reveal_type(...) # revealed: EllipsisType ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/narrow/issubclass.md b/crates/red_knot_python_semantic/resources/mdtest/narrow/issubclass.md index 7da1ad3e36126..b1099a1f7ae83 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/narrow/issubclass.md +++ b/crates/red_knot_python_semantic/resources/mdtest/narrow/issubclass.md @@ -95,10 +95,14 @@ def _(t: type[object]): ### Handling of `None` +`types.NoneType` is only available in Python 3.10 and later: + +```toml +[environment] +python-version = "3.10" +``` + ```py -# TODO: this error should ideally go away once we (1) understand `sys.version_info` branches, -# and (2) set the target Python version for this test to 3.10. -# error: [possibly-unbound-import] "Member `NoneType` of module `types` is possibly unbound" from types import NoneType def _(flag: bool): diff --git a/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md b/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md new file mode 100644 index 0000000000000..806391e0678e4 --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md @@ -0,0 +1,1162 @@ +# Statically-known branches + +## Introduction + +We have the ability to infer precise types and boundness information for symbols that are defined in +branches whose conditions we can statically determine to be always true or always false. This is +useful for `sys.version_info` branches, which can make new features available based on the Python +version: + +```py path=module1.py +import sys + +if sys.version_info >= (3, 9): + SomeFeature = "available" +``` + +If we can statically determine that the condition is always true, then we can also understand that +`SomeFeature` is always bound, without raising any errors: + +```py path=test1.py +from module1 import SomeFeature + +# SomeFeature is unconditionally available here, because we are on Python 3.9 or newer: +reveal_type(SomeFeature) # revealed: Literal["available"] +``` + +Another scenario where this is useful is for `typing.TYPE_CHECKING` branches, which are often used +for conditional imports: + +```py path=module2.py +class SomeType: ... +``` + +```py path=test2.py +import typing + +if typing.TYPE_CHECKING: + from module2 import SomeType + +# `SomeType` is unconditionally available here for type checkers: +def f(s: SomeType) -> None: ... +``` + +The rest of this document contains tests for various cases where this feature can be used. + +## If statements + +### Always false + +#### If + +```py +x = 1 + +if False: + x = 2 + +reveal_type(x) # revealed: Literal[1] +``` + +#### Else + +```py +x = 1 + +if True: + pass +else: + x = 2 + +reveal_type(x) # revealed: Literal[1] +``` + +### Always true + +#### If + +```py +x = 1 + +if True: + x = 2 + +reveal_type(x) # revealed: Literal[2] +``` + +#### Else + +```py +x = 1 + +if False: + pass +else: + x = 2 + +reveal_type(x) # revealed: Literal[2] +``` + +### Ambiguous + +Just for comparison, we still infer the combined type if the condition is not statically known: + +```py +def flag() -> bool: ... + +x = 1 + +if flag(): + x = 2 + +reveal_type(x) # revealed: Literal[1, 2] +``` + +### Combination of always true and always false + +```py +x = 1 + +if True: + x = 2 +else: + x = 3 + +reveal_type(x) # revealed: Literal[2] +``` + +### `elif` branches + +#### Always false + +```py +def flag() -> bool: ... + +x = 1 + +if flag(): + x = 2 +elif False: + x = 3 +else: + x = 4 + +reveal_type(x) # revealed: Literal[2, 4] +``` + +#### Always true + +```py +def flag() -> bool: ... + +x = 1 + +if flag(): + x = 2 +elif True: + x = 3 +else: + x = 4 + +reveal_type(x) # revealed: Literal[2, 3] +``` + +#### Ambiguous + +```py +def flag() -> bool: ... + +x = 1 + +if flag(): + x = 2 +elif flag(): + x = 3 +else: + x = 4 + +reveal_type(x) # revealed: Literal[2, 3, 4] +``` + +#### Multiple `elif` branches, always false + +Make sure that we include bindings from all non-`False` branches: + +```py +def flag() -> bool: ... + +x = 1 + +if flag(): + x = 2 +elif flag(): + x = 3 +elif False: + x = 4 +elif False: + x = 5 +elif flag(): + x = 6 +elif flag(): + x = 7 +else: + x = 8 + +reveal_type(x) # revealed: Literal[2, 3, 6, 7, 8] +``` + +#### Multiple `elif` branches, always true + +Make sure that we only include the binding from the first `elif True` branch: + +```py +def flag() -> bool: ... + +x = 1 + +if flag(): + x = 2 +elif flag(): + x = 3 +elif True: + x = 4 +elif True: + x = 5 +elif flag(): + x = 6 +else: + x = 7 + +reveal_type(x) # revealed: Literal[2, 3, 4] +``` + +#### `elif` without `else` branch + +```py +def flag() -> bool: ... + +x = 1 + +if flag(): + x = 2 +elif True: + x = 3 + +reveal_type(x) # revealed: Literal[2, 3] +``` + +### Nested conditionals + +#### `if True` inside `if True` + +```py +x = 1 + +if True: + if True: + x = 2 +else: + x = 3 + +reveal_type(x) # revealed: Literal[2] +``` + +#### `if False` inside `if True` + +```py +x = 1 + +if True: + if False: + x = 2 +else: + x = 3 + +reveal_type(x) # revealed: Literal[1] +``` + +#### `if ` inside `if True` + +```py +def flag() -> bool: ... + +x = 1 + +if True: + if flag(): + x = 2 +else: + x = 3 + +reveal_type(x) # revealed: Literal[1, 2] +``` + +#### `if True` inside `if ` + +```py +def flag() -> bool: ... + +x = 1 + +if flag(): + if True: + x = 2 +else: + x = 3 + +reveal_type(x) # revealed: Literal[2, 3] +``` + +#### `if True` inside `if False` ... `else` + +```py +x = 1 + +if False: + x = 2 +else: + if True: + x = 3 + +reveal_type(x) # revealed: Literal[3] +``` + +#### `if False` inside `if False` ... `else` + +```py +x = 1 + +if False: + x = 2 +else: + if False: + x = 3 + +reveal_type(x) # revealed: Literal[1] +``` + +#### `if ` inside `if False` ... `else` + +```py +def flag() -> bool: ... + +x = 1 + +if False: + x = 2 +else: + if flag(): + x = 3 + +reveal_type(x) # revealed: Literal[1, 3] +``` + +### Nested conditionals (with inner `else`) + +#### `if True` inside `if True` + +```py +x = 1 + +if True: + if True: + x = 2 + else: + x = 3 +else: + x = 4 + +reveal_type(x) # revealed: Literal[2] +``` + +#### `if False` inside `if True` + +```py +x = 1 + +if True: + if False: + x = 2 + else: + x = 3 +else: + x = 4 + +reveal_type(x) # revealed: Literal[3] +``` + +#### `if ` inside `if True` + +```py +def flag() -> bool: ... + +x = 1 + +if True: + if flag(): + x = 2 + else: + x = 3 +else: + x = 4 + +reveal_type(x) # revealed: Literal[2, 3] +``` + +#### `if True` inside `if ` + +```py +def flag() -> bool: ... + +x = 1 + +if flag(): + if True: + x = 2 + else: + x = 3 +else: + x = 4 + +reveal_type(x) # revealed: Literal[2, 4] +``` + +#### `if True` inside `if False` ... `else` + +```py +x = 1 + +if False: + x = 2 +else: + if True: + x = 3 + else: + x = 4 + +reveal_type(x) # revealed: Literal[3] +``` + +#### `if False` inside `if False` ... `else` + +```py +x = 1 + +if False: + x = 2 +else: + if False: + x = 3 + else: + x = 4 + +reveal_type(x) # revealed: Literal[4] +``` + +#### `if ` inside `if False` ... `else` + +```py +def flag() -> bool: ... + +x = 1 + +if False: + x = 2 +else: + if flag(): + x = 3 + else: + x = 4 + +reveal_type(x) # revealed: Literal[3, 4] +``` + +### Combination with non-conditional control flow + +#### `try` ... `except` + +##### `if True` inside `try` + +```py +def may_raise() -> None: ... + +x = 1 + +try: + may_raise() + if True: + x = 2 + else: + x = 3 +except: + x = 4 + +reveal_type(x) # revealed: Literal[2, 4] +``` + +##### `try` inside `if True` + +```py +def may_raise() -> None: ... + +x = 1 + +if True: + try: + may_raise() + x = 2 + except KeyError: + x = 3 + except ValueError: + x = 4 +else: + x = 5 + +reveal_type(x) # revealed: Literal[2, 3, 4] +``` + +##### `try` with `else` inside `if True` + +```py +def may_raise() -> None: ... + +x = 1 + +if True: + try: + may_raise() + x = 2 + except KeyError: + x = 3 + else: + x = 4 +else: + x = 5 + +reveal_type(x) # revealed: Literal[3, 4] +``` + +##### `try` with `finally` inside `if True` + +```py +def may_raise() -> None: ... + +x = 1 + +if True: + try: + may_raise() + x = 2 + except KeyError: + x = 3 + else: + x = 4 + finally: + x = 5 +else: + x = 6 + +reveal_type(x) # revealed: Literal[5] +``` + +#### `for` loops + +##### `if True` inside `for` + +```py +def iterable() -> list[object]: ... + +x = 1 + +for _ in iterable(): + x = 2 + if True: + x = 3 + +reveal_type(x) # revealed: Literal[1, 3] +``` + +##### `if True` inside `for` ... `else` + +```py +def iterable() -> list[object]: ... + +x = 1 + +for _ in iterable(): + x = 2 +else: + if True: + x = 3 + else: + x = 4 + +reveal_type(x) # revealed: Literal[3] +``` + +##### `for` inside `if True` + +```py +def iterable() -> list[object]: ... + +x = 1 + +if True: + for _ in iterable(): + x = 2 +else: + x = 3 + +reveal_type(x) # revealed: Literal[1, 2] +``` + +##### `for` ... `else` inside `if True` + +```py +def iterable() -> list[object]: ... + +x = 1 + +if True: + for _ in iterable(): + x = 2 + else: + x = 3 +else: + x = 4 + +reveal_type(x) # revealed: Literal[3] +``` + +##### `for` loop with `break` inside `if True` + +```py +def iterable() -> list[object]: ... + +x = 1 + +if True: + x = 2 + for _ in iterable(): + x = 3 + break + else: + x = 4 +else: + x = 5 + +reveal_type(x) # revealed: Literal[3, 4] +``` + +## If expressions + +See also: tests in [expression/if.md](expression/if.md). + +### Always true + +```py +x = 1 if True else 2 + +reveal_type(x) # revealed: Literal[1] +``` + +### Always false + +```py +x = 1 if False else 2 + +reveal_type(x) # revealed: Literal[2] +``` + +## Boolean expressions + +### Always true, `or` + +```py +(x := 1) or (x := 2) + +reveal_type(x) # revealed: Literal[1] +``` + +### Always true, `and` + +```py +(x := 1) and (x := 2) + +reveal_type(x) # revealed: Literal[2] +``` + +### Always false, `or` + +```py +(x := 0) or (x := 2) + +reveal_type(x) # revealed: Literal[2] +``` + +### Always false, `and` + +```py +(x := 0) and (x := 2) + +reveal_type(x) # revealed: Literal[0] +``` + +## While loops + +### Always false + +```py +x = 1 + +while False: + x = 2 + +reveal_type(x) # revealed: Literal[1] +``` + +### Always true + +```py +x = 1 + +while True: + x = 2 + break + +reveal_type(x) # revealed: Literal[2] +``` + +### Ambiguous + +Make sure that we still infer the combined type if the condition is not statically known: + +```py +def flag() -> bool: ... + +x = 1 + +while flag(): + x = 2 + +reveal_type(x) # revealed: Literal[1, 2] +``` + +### `while` ... `else` + +#### `while False` + +```py +while False: + x = 1 +else: + x = 2 + +reveal_type(x) # revealed: Literal[2] +``` + +#### `while True` + +```py +while True: + x = 1 + break +else: + x = 2 + +reveal_type(x) # revealed: Literal[1] +``` + +## `match` statements + +### Single-valued types, always true + +```py +x = 1 + +match "a": + case "a": + x = 2 + case "b": + x = 3 + +reveal_type(x) # revealed: Literal[2] +``` + +### Single-valued types, always false + +```py +x = 1 + +match "something else": + case "a": + x = 2 + case "b": + x = 3 + +reveal_type(x) # revealed: Literal[1] +``` + +### Single-valued types, with wildcard pattern + +```py +x = 1 + +match "a": + case "a": + x = 2 + case _: + x = 3 + +reveal_type(x) # revealed: Literal[2] +``` + +### Non-single-valued types + +```py +def _(s: str): + match s: + case "a": + x = 1 + case _: + x = 2 + + reveal_type(x) # revealed: Literal[1, 2] +``` + +### `sys.version_info` + +```toml +[environment] +python-version = "3.13" +``` + +```py +import sys + +minor = "too old" + +match sys.version_info.minor: + case 12: + minor = 12 + case 13: + minor = 13 + case _: + pass + +reveal_type(minor) # revealed: Literal[13] +``` + +## Conditional declarations + +### Always false + +#### `if False` + +```py +x: str + +if False: + x: int + +def f() -> None: + reveal_type(x) # revealed: str +``` + +#### `if True … else` + +```py +x: str + +if True: + pass +else: + x: int + +def f() -> None: + reveal_type(x) # revealed: str +``` + +### Always true + +#### `if True` + +```py +x: str + +if True: + x: int + +def f() -> None: + reveal_type(x) # revealed: int +``` + +#### `if False … else` + +```py +x: str + +if False: + pass +else: + x: int + +def f() -> None: + reveal_type(x) # revealed: int +``` + +### Ambiguous + +```py +def flag() -> bool: ... + +x: str + +if flag(): + x: int + +def f() -> None: + reveal_type(x) # revealed: str | int +``` + +## Conditional function definitions + +```py +def f() -> int: ... +def g() -> int: ... + +if True: + def f() -> str: ... + +else: + def g() -> str: ... + +reveal_type(f()) # revealed: str +reveal_type(g()) # revealed: int +``` + +## Conditional class definitions + +```py +if True: + class C: + x: int = 1 + +else: + class C: + x: str = "a" + +reveal_type(C.x) # revealed: int +``` + +## Conditional class attributes + +```py +class C: + if True: + x: int = 1 + else: + x: str = "a" + +reveal_type(C.x) # revealed: int +``` + +## (Un)boundness + +### Unbound, `if False` + +```py +if False: + x = 1 + +# error: [unresolved-reference] +x +``` + +### Unbound, `if True … else` + +```py +if True: + pass +else: + x = 1 + +# error: [unresolved-reference] +x +``` + +### Bound, `if True` + +```py +if True: + x = 1 + +# x is always bound, no error +x +``` + +### Bound, `if False … else` + +```py +if False: + pass +else: + x = 1 + +# x is always bound, no error +x +``` + +### Ambiguous, possibly unbound + +For comparison, we still detect definitions inside non-statically known branches as possibly +unbound: + +```py +def flag() -> bool: ... + +if flag(): + x = 1 + +# error: [possibly-unresolved-reference] +x +``` + +### Nested conditionals + +```py +def flag() -> bool: ... + +if False: + if True: + unbound1 = 1 + +if True: + if False: + unbound2 = 1 + +if False: + if False: + unbound3 = 1 + +if False: + if flag(): + unbound4 = 1 + +if flag(): + if False: + unbound5 = 1 + +# error: [unresolved-reference] +# error: [unresolved-reference] +# error: [unresolved-reference] +# error: [unresolved-reference] +# error: [unresolved-reference] +(unbound1, unbound2, unbound3, unbound4, unbound5) +``` + +### Chained conditionals + +```py +if False: + x = 1 +if True: + x = 2 + +# x is always bound, no error +x + +if False: + y = 1 +if True: + y = 2 + +# y is always bound, no error +y + +if False: + z = 1 +if False: + z = 2 + +# z is never bound: +# error: [unresolved-reference] +z +``` + +### Public boundness + +```py +if True: + x = 1 + +def f(): + # x is always bound, no error + x +``` + +### Imports of conditionally defined symbols + +#### Always false, unbound + +```py path=module.py +if False: + symbol = 1 +``` + +```py +# error: [unresolved-import] +from module import symbol +``` + +#### Always true, bound + +```py path=module.py +if True: + symbol = 1 +``` + +```py +# no error +from module import symbol +``` + +#### Ambiguous, possibly unbound + +```py path=module.py +def flag() -> bool: ... + +if flag(): + symbol = 1 +``` + +```py +# error: [possibly-unbound-import] +from module import symbol +``` + +#### Always false, undeclared + +```py path=module.py +if False: + symbol: int +``` + +```py +# error: [unresolved-import] +from module import symbol + +reveal_type(symbol) # revealed: Unknown +``` + +#### Always true, declared + +```py path=module.py +if True: + symbol: int +``` + +```py +# no error +from module import symbol +``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/subscript/tuple.md b/crates/red_knot_python_semantic/resources/mdtest/subscript/tuple.md index 1d27885567df2..88dd39144a6ae 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/subscript/tuple.md +++ b/crates/red_knot_python_semantic/resources/mdtest/subscript/tuple.md @@ -81,10 +81,7 @@ python-version = "3.9" ``` ```py -# TODO: -# * `tuple.__class_getitem__` is always bound on 3.9 (`sys.version_info`) -# * `tuple[int, str]` is a valid base (generics) -# error: [call-possibly-unbound-method] "Method `__class_getitem__` of type `Literal[tuple]` is possibly unbound" +# TODO: `tuple[int, str]` is a valid base (generics) # error: [invalid-base] "Invalid class base with type `GenericAlias` (all bases must be a class, `Any`, `Unknown` or `Todo`)" class A(tuple[int, str]): ... diff --git a/crates/red_knot_python_semantic/src/semantic_index.rs b/crates/red_knot_python_semantic/src/semantic_index.rs index 0c6ce231d1ac3..52d7b1298fcc7 100644 --- a/crates/red_knot_python_semantic/src/semantic_index.rs +++ b/crates/red_knot_python_semantic/src/semantic_index.rs @@ -27,9 +27,11 @@ pub mod definition; pub mod expression; pub mod symbol; mod use_def; +pub(crate) mod visibility_constraint; pub(crate) use self::use_def::{ BindingWithConstraints, BindingWithConstraintsIterator, DeclarationsIterator, + ScopedConstraintId, ScopedVisibilityConstraintId, }; type SymbolMap = hashbrown::HashMap; @@ -378,14 +380,16 @@ mod tests { impl UseDefMap<'_> { fn first_public_binding(&self, symbol: ScopedSymbolId) -> Option> { self.public_bindings(symbol) - .next() .map(|constrained_binding| constrained_binding.binding) + .find(Option::is_some) + .unwrap() } fn first_binding_at_use(&self, use_id: ScopedUseId) -> Option> { self.bindings_at_use(use_id) - .next() .map(|constrained_binding| constrained_binding.binding) + .find(Option::is_some) + .unwrap() } } diff --git a/crates/red_knot_python_semantic/src/semantic_index/builder.rs b/crates/red_knot_python_semantic/src/semantic_index/builder.rs index fdd6d11534195..12e85e656da82 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder.rs @@ -6,15 +6,16 @@ use rustc_hash::{FxHashMap, FxHashSet}; use ruff_db::files::File; use ruff_db::parsed::ParsedModule; use ruff_index::IndexVec; -use ruff_python_ast as ast; use ruff_python_ast::name::Name; use ruff_python_ast::visitor::{walk_expr, walk_pattern, walk_stmt, Visitor}; +use ruff_python_ast::{self as ast, Pattern}; use ruff_python_ast::{BoolOp, Expr}; use crate::ast_node_ref::AstNodeRef; use crate::module_name::ModuleName; use crate::semantic_index::ast_ids::node_key::ExpressionNodeKey; use crate::semantic_index::ast_ids::AstIdsBuilder; +use crate::semantic_index::constraint::PatternConstraintKind; use crate::semantic_index::definition::{ AssignmentDefinitionNodeRef, ComprehensionDefinitionNodeRef, Definition, DefinitionNodeKey, DefinitionNodeRef, ForStmtDefinitionNodeRef, ImportFromDefinitionNodeRef, @@ -24,7 +25,10 @@ use crate::semantic_index::symbol::{ FileScopeId, NodeWithScopeKey, NodeWithScopeRef, Scope, ScopeId, ScopedSymbolId, SymbolTableBuilder, }; -use crate::semantic_index::use_def::{FlowSnapshot, UseDefMapBuilder}; +use crate::semantic_index::use_def::{ + FlowSnapshot, ScopedConstraintId, ScopedVisibilityConstraintId, UseDefMapBuilder, +}; +use crate::semantic_index::visibility_constraint::VisibilityConstraintRef; use crate::semantic_index::SemanticIndex; use crate::unpack::Unpack; use crate::Db; @@ -157,7 +161,7 @@ impl<'db> SemanticIndexBuilder<'db> { let file_scope_id = self.scopes.push(scope); self.symbol_tables.push(SymbolTableBuilder::default()); - self.use_def_maps.push(UseDefMapBuilder::default()); + self.use_def_maps.push(UseDefMapBuilder::new()); let ast_id_scope = self.ast_ids.push(AstIdsBuilder::default()); let scope_id = ScopeId::new(self.db, self.file, file_scope_id, countme::Count::default()); @@ -279,14 +283,30 @@ impl<'db> SemanticIndexBuilder<'db> { definition } - fn record_expression_constraint(&mut self, constraint_node: &ast::Expr) -> Constraint<'db> { + fn record_expression_constraint( + &mut self, + constraint_node: &ast::Expr, + ) -> (ScopedConstraintId, Constraint<'db>) { let constraint = self.build_constraint(constraint_node); - self.record_constraint(constraint); - constraint + let constraint_id = self.record_constraint(constraint); + (constraint_id, constraint) + } + + fn record_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { + self.current_use_def_map_mut().record_constraint(constraint) } - fn record_constraint(&mut self, constraint: Constraint<'db>) { - self.current_use_def_map_mut().record_constraint(constraint); + fn record_visibility_constraint( + &mut self, + constraint: ScopedConstraintId, + ) -> ScopedVisibilityConstraintId { + self.current_use_def_map_mut() + .record_visibility_constraint(&VisibilityConstraintRef::Single(constraint)) + } + + fn record_negated_visibility_constraint(&mut self, constraint: ScopedVisibilityConstraintId) { + self.current_use_def_map_mut() + .record_visibility_constraint(&VisibilityConstraintRef::Negated(constraint)); } fn build_constraint(&mut self, constraint_node: &Expr) -> Constraint<'db> { @@ -297,12 +317,13 @@ impl<'db> SemanticIndexBuilder<'db> { } } - fn record_negated_constraint(&mut self, constraint: Constraint<'db>) { - self.current_use_def_map_mut() - .record_constraint(Constraint { - node: constraint.node, - is_positive: false, - }); + fn record_negated_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { + let negated = Constraint { + node: constraint.node, + is_positive: false, + }; + let constraint_id = self.current_use_def_map_mut().record_constraint(negated); + constraint_id } fn push_assignment(&mut self, assignment: CurrentAssignment<'db>) { @@ -324,30 +345,31 @@ impl<'db> SemanticIndexBuilder<'db> { fn add_pattern_constraint( &mut self, - subject: &ast::Expr, + subject: Expression<'db>, pattern: &ast::Pattern, - ) -> PatternConstraint<'db> { - #[allow(unsafe_code)] - let (subject, pattern) = unsafe { - ( - AstNodeRef::new(self.module.clone(), subject), - AstNodeRef::new(self.module.clone(), pattern), - ) + ) -> ScopedConstraintId { + let kind = match pattern { + Pattern::MatchValue(pattern) => { + let value = self.add_standalone_expression(&pattern.value); + PatternConstraintKind::Value(value) + } + Pattern::MatchSingleton(singleton) => PatternConstraintKind::Singleton(singleton.value), + _ => PatternConstraintKind::Unsupported, }; + let pattern_constraint = PatternConstraint::new( self.db, self.file, self.current_scope(), subject, - pattern, + kind, countme::Count::default(), ); self.current_use_def_map_mut() .record_constraint(Constraint { node: ConstraintNode::Pattern(pattern_constraint), is_positive: true, - }); - pattern_constraint + }) } /// Record an expression that needs to be a Salsa ingredient, because we need to infer its type @@ -796,9 +818,13 @@ where ast::Stmt::If(node) => { self.visit_expr(&node.test); let pre_if = self.flow_snapshot(); - let constraint = self.record_expression_constraint(&node.test); - let mut constraints = vec![constraint]; + let (constraint_id, constraint) = self.record_expression_constraint(&node.test); + let mut constraints = vec![(constraint_id, constraint)]; self.visit_body(&node.body); + + let visibility_constraint_id = self.record_visibility_constraint(constraint_id); + let mut vis_constraints = vec![visibility_constraint_id]; + let mut post_clauses: Vec = vec![]; let elif_else_clauses = node .elif_else_clauses @@ -822,15 +848,30 @@ where // we can only take an elif/else branch if none of the previous ones were // taken, so the block entry state is always `pre_if` self.flow_restore(pre_if.clone()); - for constraint in &constraints { + for (_, constraint) in &constraints { self.record_negated_constraint(*constraint); } - if let Some(elif_test) = clause_test { + + let elif_constraint = if let Some(elif_test) = clause_test { self.visit_expr(elif_test); - constraints.push(self.record_expression_constraint(elif_test)); - } + let (constraint_id, constraint) = + self.record_expression_constraint(elif_test); + constraints.push((constraint_id, constraint)); + Some(constraint_id) + } else { + None + }; self.visit_body(clause_body); + + for id in &vis_constraints { + self.record_negated_visibility_constraint(*id); + } + if let Some(elif_constraint) = elif_constraint { + let id = self.record_visibility_constraint(elif_constraint); + vis_constraints.push(id); + } } + for post_clause_state in post_clauses { self.flow_merge(post_clause_state); } @@ -844,7 +885,7 @@ where self.visit_expr(test); let pre_loop = self.flow_snapshot(); - let constraint = self.record_expression_constraint(test); + let (constraint_id, constraint) = self.record_expression_constraint(test); // Save aside any break states from an outer loop let saved_break_states = std::mem::take(&mut self.loop_break_states); @@ -861,12 +902,16 @@ where let break_states = std::mem::replace(&mut self.loop_break_states, saved_break_states); + let vis_constraint_id = self.record_visibility_constraint(constraint_id); + // We may execute the `else` clause without ever executing the body, so merge in // the pre-loop state before visiting `else`. self.flow_merge(pre_loop); self.record_negated_constraint(constraint); self.visit_body(orelse); + self.record_negated_visibility_constraint(vis_constraint_id); + // Breaking out of a while loop bypasses the `else` clause, so merge in the break // states after visiting `else`. for break_state in break_states { @@ -947,22 +992,34 @@ where cases, range: _, }) => { - self.add_standalone_expression(subject); + let subject_expr = self.add_standalone_expression(subject); self.visit_expr(subject); let after_subject = self.flow_snapshot(); let Some((first, remaining)) = cases.split_first() else { return; }; - self.add_pattern_constraint(subject, &first.pattern); + + let first_constraint_id = self.add_pattern_constraint(subject_expr, &first.pattern); + self.visit_match_case(first); + let first_vis_constraint_id = + self.record_visibility_constraint(first_constraint_id); + let mut vis_constraints = vec![first_vis_constraint_id]; + let mut post_case_snapshots = vec![]; for case in remaining { post_case_snapshots.push(self.flow_snapshot()); self.flow_restore(after_subject.clone()); - self.add_pattern_constraint(subject, &case.pattern); + let constraint_id = self.add_pattern_constraint(subject_expr, &case.pattern); self.visit_match_case(case); + + for id in &vis_constraints { + self.record_negated_visibility_constraint(*id); + } + let vis_constraint_id = self.record_visibility_constraint(constraint_id); + vis_constraints.push(vis_constraint_id); } for post_clause_state in post_case_snapshots { self.flow_merge(post_clause_state); @@ -972,6 +1029,14 @@ where .is_some_and(|case| case.guard.is_none() && case.pattern.is_wildcard()) { self.flow_merge(after_subject); + + // for post_clause_state in post_case_snapshots { + // self.flow_merge(post_clause_state); + // } + + for id in &vis_constraints { + self.record_negated_visibility_constraint(*id); + } } } ast::Stmt::Try(ast::StmtTry { @@ -1222,12 +1287,9 @@ where ast::Expr::If(ast::ExprIf { body, test, orelse, .. }) => { - // TODO detect statically known truthy or falsy test (via type inference, not naive - // AST inspection, so we can't simplify here, need to record test expression for - // later checking) self.visit_expr(test); let pre_if = self.flow_snapshot(); - let constraint = self.record_expression_constraint(test); + let (_, constraint) = self.record_expression_constraint(test); self.visit_expr(body); let post_body = self.flow_snapshot(); self.flow_restore(pre_if); @@ -1291,22 +1353,22 @@ where range: _, op, }) => { - // TODO detect statically known truthy or falsy values (via type inference, not naive - // AST inspection, so we can't simplify here, need to record test expression for - // later checking) let mut snapshots = vec![]; - + let mut last_constraint_id = None; for (index, value) in values.iter().enumerate() { self.visit_expr(value); + if let Some(last_constraint_id) = last_constraint_id { + self.record_visibility_constraint(last_constraint_id); + } // In the last value we don't need to take a snapshot nor add a constraint if index < values.len() - 1 { // Snapshot is taken after visiting the expression but before adding the constraint. snapshots.push(self.flow_snapshot()); let constraint = self.build_constraint(value); - match op { + last_constraint_id = Some(match op { BoolOp::And => self.record_constraint(constraint), BoolOp::Or => self.record_negated_constraint(constraint), - } + }); } } for snapshot in snapshots { diff --git a/crates/red_knot_python_semantic/src/semantic_index/constraint.rs b/crates/red_knot_python_semantic/src/semantic_index/constraint.rs index 44b542f0e90ac..347f0ebaac4f7 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/constraint.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/constraint.rs @@ -1,7 +1,6 @@ use ruff_db::files::File; -use ruff_python_ast as ast; +use ruff_python_ast::Singleton; -use crate::ast_node_ref::AstNodeRef; use crate::db::Db; use crate::semantic_index::expression::Expression; use crate::semantic_index::symbol::{FileScopeId, ScopeId}; @@ -18,6 +17,14 @@ pub(crate) enum ConstraintNode<'db> { Pattern(PatternConstraint<'db>), } +/// Pattern kinds for which we do support type narrowing and/or static truthiness analysis. +#[derive(Debug, Clone, PartialEq)] +pub(crate) enum PatternConstraintKind<'db> { + Singleton(Singleton), + Value(Expression<'db>), + Unsupported, +} + #[salsa::tracked] pub(crate) struct PatternConstraint<'db> { #[id] @@ -28,11 +35,11 @@ pub(crate) struct PatternConstraint<'db> { #[no_eq] #[return_ref] - pub(crate) subject: AstNodeRef, + pub(crate) subject: Expression<'db>, #[no_eq] #[return_ref] - pub(crate) pattern: AstNodeRef, + pub(crate) kind: PatternConstraintKind<'db>, #[no_eq] count: countme::Count>, diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index 9f3e197c74eee..4364bbeb5a031 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -223,12 +223,13 @@ //! visits a `StmtIf` node. use self::symbol_state::{ BindingIdWithConstraintsIterator, ConstraintIdIterator, DeclarationIdIterator, - ScopedConstraintId, ScopedDefinitionId, SymbolBindings, SymbolDeclarations, SymbolState, + ScopedDefinitionId, SymbolBindings, SymbolDeclarations, SymbolState, }; +pub(crate) use self::symbol_state::{ScopedConstraintId, ScopedVisibilityConstraintId}; use crate::semantic_index::ast_ids::ScopedUseId; use crate::semantic_index::definition::Definition; use crate::semantic_index::symbol::ScopedSymbolId; -use crate::symbol::Boundness; +pub(crate) use crate::semantic_index::visibility_constraint::VisibilityConstraintRef; use ruff_index::IndexVec; use rustc_hash::FxHashMap; @@ -241,11 +242,14 @@ mod symbol_state; #[derive(Debug, PartialEq, Eq)] pub(crate) struct UseDefMap<'db> { /// Array of [`Definition`] in this scope. - all_definitions: IndexVec>, + all_definitions: IndexVec>>, /// Array of [`Constraint`] in this scope. all_constraints: IndexVec>, + /// Array of [`VisibilityConstraintRef`] in this scope. + all_visibility_constraints: IndexVec, + /// [`SymbolBindings`] reaching a [`ScopedUseId`]. bindings_by_use: IndexVec, @@ -275,14 +279,6 @@ impl<'db> UseDefMap<'db> { self.bindings_iterator(&self.bindings_by_use[use_id]) } - pub(crate) fn use_boundness(&self, use_id: ScopedUseId) -> Boundness { - if self.bindings_by_use[use_id].may_be_unbound() { - Boundness::PossiblyUnbound - } else { - Boundness::Bound - } - } - pub(crate) fn public_bindings( &self, symbol: ScopedSymbolId, @@ -290,14 +286,6 @@ impl<'db> UseDefMap<'db> { self.bindings_iterator(self.public_symbols[symbol].bindings()) } - pub(crate) fn public_boundness(&self, symbol: ScopedSymbolId) -> Boundness { - if self.public_symbols[symbol].may_be_unbound() { - Boundness::PossiblyUnbound - } else { - Boundness::Bound - } - } - pub(crate) fn bindings_at_declaration( &self, declaration: Definition<'db>, @@ -310,10 +298,10 @@ impl<'db> UseDefMap<'db> { } } - pub(crate) fn declarations_at_binding( - &self, + pub(crate) fn declarations_at_binding<'map>( + &'map self, binding: Definition<'db>, - ) -> DeclarationsIterator<'_, 'db> { + ) -> DeclarationsIterator<'map, 'db> { if let SymbolDefinitions::Declarations(declarations) = &self.definitions_by_definition[&binding] { @@ -323,37 +311,39 @@ impl<'db> UseDefMap<'db> { } } - pub(crate) fn public_declarations( - &self, + pub(crate) fn public_declarations<'map>( + &'map self, symbol: ScopedSymbolId, - ) -> DeclarationsIterator<'_, 'db> { + ) -> DeclarationsIterator<'map, 'db> { let declarations = self.public_symbols[symbol].declarations(); self.declarations_iterator(declarations) } - pub(crate) fn has_public_declarations(&self, symbol: ScopedSymbolId) -> bool { - !self.public_symbols[symbol].declarations().is_empty() - } - - fn bindings_iterator<'a>( - &'a self, - bindings: &'a SymbolBindings, - ) -> BindingWithConstraintsIterator<'a, 'db> { + fn bindings_iterator<'map>( + &'map self, + bindings: &'map SymbolBindings, + ) -> BindingWithConstraintsIterator<'map, 'db> { BindingWithConstraintsIterator { all_definitions: &self.all_definitions, all_constraints: &self.all_constraints, - inner: bindings.iter(), + inner: bindings.iter(&self.all_constraints, &self.all_visibility_constraints), } } - fn declarations_iterator<'a>( - &'a self, - declarations: &'a SymbolDeclarations, - ) -> DeclarationsIterator<'a, 'db> { + fn declarations_iterator<'map>( + &'map self, + declarations: &'map SymbolDeclarations, + ) -> DeclarationsIterator<'map, 'db> { DeclarationsIterator { all_definitions: &self.all_definitions, - inner: declarations.iter(), - may_be_undeclared: declarations.may_be_undeclared(), + inner: { + DeclarationIdIterator { + all_constraints: &self.all_constraints, + all_visibility_constraints: &self.all_visibility_constraints, + inner: declarations.live_declarations.iter(), + visibility_constraints: declarations.visibility_constraints.iter(), + } + }, } } } @@ -367,23 +357,28 @@ enum SymbolDefinitions { #[derive(Debug)] pub(crate) struct BindingWithConstraintsIterator<'map, 'db> { - all_definitions: &'map IndexVec>, + all_definitions: &'map IndexVec>>, all_constraints: &'map IndexVec>, - inner: BindingIdWithConstraintsIterator<'map>, + inner: BindingIdWithConstraintsIterator<'map, 'db>, } impl<'map, 'db> Iterator for BindingWithConstraintsIterator<'map, 'db> { type Item = BindingWithConstraints<'map, 'db>; fn next(&mut self) -> Option { + let all_constraints = self.all_constraints; + self.inner .next() - .map(|def_id_with_constraints| BindingWithConstraints { - binding: self.all_definitions[def_id_with_constraints.definition], + .map(|binding_id_with_constraints| BindingWithConstraints { + binding: self.all_definitions[binding_id_with_constraints.definition], constraints: ConstraintsIterator { - all_constraints: self.all_constraints, - constraint_ids: def_id_with_constraints.constraint_ids, + all_constraints, + constraint_ids: binding_id_with_constraints.constraint_ids, }, + all_constraints: binding_id_with_constraints.all_constraints, + all_visibility_constraints: binding_id_with_constraints.all_visibility_constraints, + visibility_constraint: binding_id_with_constraints.visibility_constraint, }) } } @@ -391,8 +386,12 @@ impl<'map, 'db> Iterator for BindingWithConstraintsIterator<'map, 'db> { impl std::iter::FusedIterator for BindingWithConstraintsIterator<'_, '_> {} pub(crate) struct BindingWithConstraints<'map, 'db> { - pub(crate) binding: Definition<'db>, + pub(crate) binding: Option>, pub(crate) constraints: ConstraintsIterator<'map, 'db>, + pub(crate) all_constraints: &'map IndexVec>, + pub(crate) all_visibility_constraints: + &'map IndexVec, + pub(crate) visibility_constraint: ScopedVisibilityConstraintId, } pub(crate) struct ConstraintsIterator<'map, 'db> { @@ -413,22 +412,29 @@ impl<'db> Iterator for ConstraintsIterator<'_, 'db> { impl std::iter::FusedIterator for ConstraintsIterator<'_, '_> {} pub(crate) struct DeclarationsIterator<'map, 'db> { - all_definitions: &'map IndexVec>, - inner: DeclarationIdIterator<'map>, - may_be_undeclared: bool, -} - -impl DeclarationsIterator<'_, '_> { - pub(crate) fn may_be_undeclared(&self) -> bool { - self.may_be_undeclared - } + all_definitions: &'map IndexVec>>, + inner: DeclarationIdIterator<'map, 'db>, } -impl<'db> Iterator for DeclarationsIterator<'_, 'db> { - type Item = Definition<'db>; +impl<'map, 'db> Iterator for DeclarationsIterator<'map, 'db> { + type Item = ( + Option>, + &'map IndexVec>, + &'map IndexVec, + ScopedVisibilityConstraintId, + ); fn next(&mut self) -> Option { - self.inner.next().map(|def_id| self.all_definitions[def_id]) + self.inner.next().map( + |(def_id, all_constraints, all_visibility_constraints, visibility_constraint_id)| { + ( + self.all_definitions[def_id], + all_constraints, + all_visibility_constraints, + visibility_constraint_id, + ) + }, + ) } } @@ -438,16 +444,22 @@ impl std::iter::FusedIterator for DeclarationsIterator<'_, '_> {} #[derive(Clone, Debug)] pub(super) struct FlowSnapshot { symbol_states: IndexVec, + unbound_visibility_constraint_id: ScopedVisibilityConstraintId, } -#[derive(Debug, Default)] +#[derive(Debug)] pub(super) struct UseDefMapBuilder<'db> { /// Append-only array of [`Definition`]. - all_definitions: IndexVec>, + all_definitions: IndexVec>>, /// Append-only array of [`Constraint`]. all_constraints: IndexVec>, + /// Append-only array of [`VisibilityConstraintRef`]. + all_visibility_constraints: IndexVec, + + unbound_visibility_constraint_id: ScopedVisibilityConstraintId, + /// Live bindings at each so-far-recorded use. bindings_by_use: IndexVec, @@ -459,13 +471,27 @@ pub(super) struct UseDefMapBuilder<'db> { } impl<'db> UseDefMapBuilder<'db> { + pub(super) fn new() -> Self { + Self { + all_definitions: IndexVec::from_iter([None]), + all_constraints: IndexVec::new(), + all_visibility_constraints: IndexVec::from_iter([VisibilityConstraintRef::None]), + unbound_visibility_constraint_id: ScopedVisibilityConstraintId::from_u32(0), + bindings_by_use: IndexVec::new(), + definitions_by_definition: FxHashMap::default(), + symbol_states: IndexVec::new(), + } + } + pub(super) fn add_symbol(&mut self, symbol: ScopedSymbolId) { - let new_symbol = self.symbol_states.push(SymbolState::undefined()); + let new_symbol = self.symbol_states.push(SymbolState::undefined( + self.unbound_visibility_constraint_id, + )); 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(binding); + let def_id = self.all_definitions.push(Some(binding)); let symbol_state = &mut self.symbol_states[symbol]; self.definitions_by_definition.insert( binding, @@ -474,11 +500,38 @@ impl<'db> UseDefMapBuilder<'db> { symbol_state.record_binding(def_id); } - pub(super) fn record_constraint(&mut self, constraint: Constraint<'db>) { + pub(super) fn record_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { let constraint_id = self.all_constraints.push(constraint); for state in &mut self.symbol_states { state.record_constraint(constraint_id); } + constraint_id + } + + pub(super) fn record_visibility_constraint( + &mut self, + constraint: &VisibilityConstraintRef, + ) -> ScopedVisibilityConstraintId { + let new_constraint_id = self.all_visibility_constraints.push(constraint.clone()); + for state in &mut self.symbol_states { + state.record_visibility_constraint( + &mut self.all_visibility_constraints, + new_constraint_id, + ); + } + + if self.unbound_visibility_constraint_id == ScopedVisibilityConstraintId::from_u32(0) { + self.unbound_visibility_constraint_id = new_constraint_id; + } else { + self.unbound_visibility_constraint_id = + self.all_visibility_constraints + .push(VisibilityConstraintRef::And( + self.unbound_visibility_constraint_id, + new_constraint_id, + )); + } + + new_constraint_id } pub(super) fn record_declaration( @@ -486,7 +539,7 @@ impl<'db> UseDefMapBuilder<'db> { symbol: ScopedSymbolId, declaration: Definition<'db>, ) { - let def_id = self.all_definitions.push(declaration); + let def_id = self.all_definitions.push(Some(declaration)); let symbol_state = &mut self.symbol_states[symbol]; self.definitions_by_definition.insert( declaration, @@ -501,7 +554,7 @@ impl<'db> UseDefMapBuilder<'db> { definition: Definition<'db>, ) { // We don't need to store anything in self.definitions_by_definition. - let def_id = self.all_definitions.push(definition); + 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); @@ -520,6 +573,7 @@ impl<'db> UseDefMapBuilder<'db> { pub(super) fn snapshot(&self) -> FlowSnapshot { FlowSnapshot { symbol_states: self.symbol_states.clone(), + unbound_visibility_constraint_id: self.unbound_visibility_constraint_id, } } @@ -533,12 +587,15 @@ impl<'db> UseDefMapBuilder<'db> { // Restore the current visible-definitions state to the given snapshot. self.symbol_states = snapshot.symbol_states; + self.unbound_visibility_constraint_id = snapshot.unbound_visibility_constraint_id; // 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 // snapshot, the correct state to fill them in with is "undefined". - self.symbol_states - .resize(num_symbols, SymbolState::undefined()); + self.symbol_states.resize( + num_symbols, + SymbolState::undefined(self.unbound_visibility_constraint_id), + ); } /// Merge the given snapshot into the current state, reflecting that we might have taken either @@ -553,11 +610,40 @@ impl<'db> UseDefMapBuilder<'db> { let mut snapshot_definitions_iter = snapshot.symbol_states.into_iter(); for current in &mut self.symbol_states { if let Some(snapshot) = snapshot_definitions_iter.next() { - current.merge(snapshot); + current.merge(snapshot, &mut self.all_visibility_constraints); } else { + current.merge( + SymbolState::undefined(snapshot.unbound_visibility_constraint_id), + &mut self.all_visibility_constraints, + ); // Symbol not present in snapshot, so it's unbound/undeclared from that path. - current.set_may_be_unbound(); - current.set_may_be_undeclared(); + } + } + + // Merge unbound visibility constraints: + match ( + &self.all_visibility_constraints[self.unbound_visibility_constraint_id], + &self.all_visibility_constraints[snapshot.unbound_visibility_constraint_id], + ) { + (_, VisibilityConstraintRef::Negated(id)) + if self.unbound_visibility_constraint_id == *id => + { + self.unbound_visibility_constraint_id = ScopedVisibilityConstraintId::from_u32(0); + } + + (VisibilityConstraintRef::Negated(id), _) + if *id == snapshot.unbound_visibility_constraint_id => + { + self.unbound_visibility_constraint_id = ScopedVisibilityConstraintId::from_u32(0); + } + _ => { + let constraint_id = + self.all_visibility_constraints + .push(VisibilityConstraintRef::Or( + self.unbound_visibility_constraint_id, + snapshot.unbound_visibility_constraint_id, + )); + self.unbound_visibility_constraint_id = constraint_id; } } } @@ -572,6 +658,7 @@ impl<'db> UseDefMapBuilder<'db> { UseDefMap { all_definitions: self.all_definitions, all_constraints: self.all_constraints, + all_visibility_constraints: self.all_visibility_constraints, bindings_by_use: self.bindings_by_use, public_symbols: self.symbol_states, definitions_by_definition: self.definitions_by_definition, diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def/bitset.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def/bitset.rs index 464f718e7b4f4..bf7bb01365c02 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def/bitset.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def/bitset.rs @@ -32,10 +32,6 @@ impl BitSet { bitset } - pub(super) fn is_empty(&self) -> bool { - self.blocks().iter().all(|&b| b == 0) - } - /// Convert from Inline to Heap, if needed, and resize the Heap vector, if needed. fn resize(&mut self, value: u32) { let num_blocks_needed = (value / 64) + 1; @@ -97,19 +93,6 @@ impl BitSet { } } - /// Union in-place with another [`BitSet`]. - pub(super) fn union(&mut self, other: &BitSet) { - let mut max_len = self.blocks().len(); - let other_len = other.blocks().len(); - if other_len > max_len { - max_len = other_len; - self.resize_blocks(max_len); - } - for (my_block, other_block) in self.blocks_mut().iter_mut().zip(other.blocks()) { - *my_block |= other_block; - } - } - /// Return an iterator over the values (in ascending order) in this [`BitSet`]. pub(super) fn iter(&self) -> BitSetIterator<'_, B> { let blocks = self.blocks(); @@ -239,59 +222,6 @@ mod tests { assert_bitset(&b1, &[89]); } - #[test] - fn union() { - let mut b1 = BitSet::<1>::with(2); - let b2 = BitSet::<1>::with(4); - - b1.union(&b2); - assert_bitset(&b1, &[2, 4]); - } - - #[test] - fn union_mixed_1() { - let mut b1 = BitSet::<1>::with(4); - let mut b2 = BitSet::<1>::with(4); - b1.insert(89); - b2.insert(5); - - b1.union(&b2); - assert_bitset(&b1, &[4, 5, 89]); - } - - #[test] - fn union_mixed_2() { - let mut b1 = BitSet::<1>::with(4); - let mut b2 = BitSet::<1>::with(4); - b1.insert(23); - b2.insert(89); - - b1.union(&b2); - assert_bitset(&b1, &[4, 23, 89]); - } - - #[test] - fn union_heap() { - let mut b1 = BitSet::<1>::with(4); - let mut b2 = BitSet::<1>::with(4); - b1.insert(89); - b2.insert(90); - - b1.union(&b2); - assert_bitset(&b1, &[4, 89, 90]); - } - - #[test] - fn union_heap_2() { - let mut b1 = BitSet::<1>::with(89); - let mut b2 = BitSet::<1>::with(89); - b1.insert(91); - b2.insert(90); - - b1.union(&b2); - assert_bitset(&b1, &[89, 90, 91]); - } - #[test] fn multiple_blocks() { let mut b = BitSet::<2>::with(120); @@ -299,11 +229,4 @@ mod tests { assert!(matches!(b, BitSet::Inline(_))); assert_bitset(&b, &[45, 120]); } - - #[test] - fn empty() { - let b = BitSet::<1>::default(); - - assert!(b.is_empty()); - } } diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs index 506300067c952..e1315d532af43 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs @@ -43,8 +43,10 @@ //! //! Tracking live declarations is simpler, since constraints are not involved, but otherwise very //! similar to tracking live bindings. +use crate::semantic_index::use_def::{Constraint, VisibilityConstraintRef}; + use super::bitset::{BitSet, BitSetIterator}; -use ruff_index::newtype_index; +use ruff_index::{newtype_index, IndexVec}; use smallvec::SmallVec; /// A newtype-index for a definition in a particular scope. @@ -53,7 +55,7 @@ pub(super) struct ScopedDefinitionId; /// A newtype-index for a constraint expression in a particular scope. #[newtype_index] -pub(super) struct ScopedConstraintId; +pub(crate) struct ScopedConstraintId; /// Can reference this * 64 total definitions inline; more will fall back to the heap. const INLINE_BINDING_BLOCKS: usize = 3; @@ -75,55 +77,91 @@ const INLINE_CONSTRAINT_BLOCKS: usize = 2; /// Can keep inline this many live bindings per symbol at a given time; more will go to heap. const INLINE_BINDINGS_PER_SYMBOL: usize = 4; -/// One [`BitSet`] of applicable [`ScopedConstraintId`] per live binding. -type InlineConstraintArray = [BitSet; INLINE_BINDINGS_PER_SYMBOL]; -type Constraints = SmallVec; -type ConstraintsIterator<'a> = std::slice::Iter<'a, BitSet>; +/// Which constraints apply to a given binding? +type Constraints = BitSet; + +type InlineConstraintArray = [Constraints; INLINE_BINDINGS_PER_SYMBOL]; + +/// One [`BitSet`] of applicable [`ScopedConstraintId`]s per live binding. +type ConstraintsPerBinding = SmallVec; + +/// Iterate over all constraints for a single binding. +type ConstraintsIterator<'a> = std::slice::Iter<'a, Constraints>; type ConstraintsIntoIterator = smallvec::IntoIter; +/// Similar to what we have above, but for visibility constraints. +#[newtype_index] +pub(crate) struct ScopedVisibilityConstraintId; +const INLINE_VISIBILITY_CONSTRAINTS: usize = 4; +type InlineVisibilityConstraintsArray = + [ScopedVisibilityConstraintId; INLINE_VISIBILITY_CONSTRAINTS]; +type VisibilityConstraintPerBinding = SmallVec; +type VisibilityConstraintsIterator<'a> = std::slice::Iter<'a, ScopedVisibilityConstraintId>; +type VisibilityConstraintsIntoIterator = smallvec::IntoIter; + /// Live declarations for a single symbol at some point in control flow. #[derive(Clone, Debug, PartialEq, Eq)] pub(super) struct SymbolDeclarations { /// [`BitSet`]: which declarations (as [`ScopedDefinitionId`]) can reach the current location? - live_declarations: Declarations, + pub(crate) live_declarations: Declarations, - /// Could the symbol be un-declared at this point? - may_be_undeclared: bool, + /// For each live declaration, which [`VisibilityConstraints`] were active at that declaration? + pub(crate) visibility_constraints: VisibilityConstraintPerBinding, } impl SymbolDeclarations { - fn undeclared() -> Self { + fn undeclared(unbound_visibility_constraint_id: ScopedVisibilityConstraintId) -> Self { Self { - live_declarations: Declarations::default(), - may_be_undeclared: true, + live_declarations: Declarations::with(0), + visibility_constraints: VisibilityConstraintPerBinding::from_iter([ + unbound_visibility_constraint_id, + ]), } } /// Record a newly-encountered declaration for this symbol. fn record_declaration(&mut self, declaration_id: ScopedDefinitionId) { self.live_declarations = Declarations::with(declaration_id.into()); - self.may_be_undeclared = false; - } - /// Add undeclared as a possibility for this symbol. - fn set_may_be_undeclared(&mut self) { - self.may_be_undeclared = true; + self.visibility_constraints = VisibilityConstraintPerBinding::with_capacity(1); + self.visibility_constraints + .push(ScopedVisibilityConstraintId::from_u32(0)); } - /// Return an iterator over live declarations for this symbol. - pub(super) fn iter(&self) -> DeclarationIdIterator { - DeclarationIdIterator { - inner: self.live_declarations.iter(), + /// Add given visibility constraint to all live bindings. + pub(super) fn record_visibility_constraint( + &mut self, + all_visibility_constraints: &mut IndexVec< + ScopedVisibilityConstraintId, + VisibilityConstraintRef, + >, + constraint: ScopedVisibilityConstraintId, + ) { + for existing in &mut self.visibility_constraints { + if existing == &ScopedVisibilityConstraintId::from_u32(0) { + *existing = constraint; + } else { + *existing = all_visibility_constraints + .push(VisibilityConstraintRef::And(*existing, constraint)); + } } } - pub(super) fn is_empty(&self) -> bool { - self.live_declarations.is_empty() - } - - pub(super) fn may_be_undeclared(&self) -> bool { - self.may_be_undeclared - } + // /// Return an iterator over live declarations for this symbol. + // pub(super) fn iter<'map, 'db>( + // &'db self, + // all_constraints: &'map IndexVec>, + // ) -> DeclarationIdIterator<'map, 'db> { + // DeclarationIdIterator { + // all_constraints, + // inner: self.live_declarations.iter(), + // visibility_constraints: self.visibility_constraints.iter(), + // } + // } + + // pub(super) fn is_empty(&self) -> bool { + // self.live_declarations.is_empty() + // } } /// Live bindings and narrowing constraints for a single symbol at some point in control flow. @@ -136,34 +174,34 @@ pub(super) struct SymbolBindings { /// /// This is a [`smallvec::SmallVec`] which should always have one [`BitSet`] of constraints per /// binding in `live_bindings`. - constraints: Constraints, + constraints: ConstraintsPerBinding, - /// Could the symbol be unbound at this point? - may_be_unbound: bool, + /// For each live binding, which [`VisibilityConstraints`] were active at that binding? + visibility_constraints: VisibilityConstraintPerBinding, } impl SymbolBindings { - fn unbound() -> Self { + fn unbound(unbound_visibility_constraint_id: ScopedVisibilityConstraintId) -> Self { Self { - live_bindings: Bindings::default(), - constraints: Constraints::default(), - may_be_unbound: true, + live_bindings: Bindings::with(0), + constraints: ConstraintsPerBinding::from_iter([Constraints::default()]), + visibility_constraints: VisibilityConstraintPerBinding::from_iter([ + unbound_visibility_constraint_id, + ]), } } - /// Add Unbound as a possibility for this symbol. - fn set_may_be_unbound(&mut self) { - self.may_be_unbound = true; - } - /// Record a newly-encountered binding for this symbol. pub(super) fn record_binding(&mut self, binding_id: ScopedDefinitionId) { // The new binding replaces all previous live bindings in this path, and has no // constraints. self.live_bindings = Bindings::with(binding_id.into()); - self.constraints = Constraints::with_capacity(1); - self.constraints.push(BitSet::default()); - self.may_be_unbound = false; + self.constraints = ConstraintsPerBinding::with_capacity(1); + self.constraints.push(Constraints::default()); + + self.visibility_constraints = VisibilityConstraintPerBinding::with_capacity(1); + self.visibility_constraints + .push(ScopedVisibilityConstraintId::from_u32(0)); } /// Add given constraint to all live bindings. @@ -173,17 +211,42 @@ impl SymbolBindings { } } - /// Iterate over currently live bindings for this symbol. - pub(super) fn iter(&self) -> BindingIdWithConstraintsIterator { + /// Add given visibility constraint to all live bindings. + pub(super) fn record_visibility_constraint( + &mut self, + all_visibility_constraints: &mut IndexVec< + ScopedVisibilityConstraintId, + VisibilityConstraintRef, + >, + constraint: ScopedVisibilityConstraintId, + ) { + for existing in &mut self.visibility_constraints { + if existing == &ScopedVisibilityConstraintId::from_u32(0) { + *existing = constraint; + } else { + *existing = all_visibility_constraints + .push(VisibilityConstraintRef::And(*existing, constraint)); + } + } + } + + /// Iterate over currently live bindings for this symbol + pub(super) fn iter<'map, 'db>( + &'map self, + all_constraints: &'map IndexVec>, + all_visibility_constraints: &'map IndexVec< + ScopedVisibilityConstraintId, + VisibilityConstraintRef, + >, + ) -> BindingIdWithConstraintsIterator<'map, 'db> { BindingIdWithConstraintsIterator { + all_constraints, + all_visibility_constraints, definitions: self.live_bindings.iter(), constraints: self.constraints.iter(), + visibility_constraints: self.visibility_constraints.iter(), } } - - pub(super) fn may_be_unbound(&self) -> bool { - self.may_be_unbound - } } #[derive(Clone, Debug, PartialEq, Eq)] @@ -194,18 +257,13 @@ pub(super) struct SymbolState { impl SymbolState { /// Return a new [`SymbolState`] representing an unbound, undeclared symbol. - pub(super) fn undefined() -> Self { + pub(super) fn undefined(unbound_visibility_constraint: ScopedVisibilityConstraintId) -> Self { Self { - declarations: SymbolDeclarations::undeclared(), - bindings: SymbolBindings::unbound(), + declarations: SymbolDeclarations::undeclared(unbound_visibility_constraint), + bindings: SymbolBindings::unbound(unbound_visibility_constraint), } } - /// Add Unbound as a possibility for this symbol. - pub(super) fn set_may_be_unbound(&mut self) { - self.bindings.set_may_be_unbound(); - } - /// Record a newly-encountered binding for this symbol. pub(super) fn record_binding(&mut self, binding_id: ScopedDefinitionId) { self.bindings.record_binding(binding_id); @@ -216,9 +274,19 @@ impl SymbolState { self.bindings.record_constraint(constraint_id); } - /// Add undeclared as a possibility for this symbol. - pub(super) fn set_may_be_undeclared(&mut self) { - self.declarations.set_may_be_undeclared(); + /// Add given visibility constraint to all live bindings. + pub(super) fn record_visibility_constraint( + &mut self, + all_visibility_constraints: &mut IndexVec< + ScopedVisibilityConstraintId, + VisibilityConstraintRef, + >, + constraint: ScopedVisibilityConstraintId, + ) { + self.bindings + .record_visibility_constraint(all_visibility_constraints, constraint); + self.declarations + .record_visibility_constraint(all_visibility_constraints, constraint); } /// Record a newly-encountered declaration of this symbol. @@ -227,29 +295,34 @@ impl SymbolState { } /// Merge another [`SymbolState`] into this one. - pub(super) fn merge(&mut self, b: SymbolState) { + pub(super) fn merge( + &mut self, + b: SymbolState, + all_visibility_constraints: &mut IndexVec< + ScopedVisibilityConstraintId, + VisibilityConstraintRef, + >, + ) { let mut a = Self { bindings: SymbolBindings { live_bindings: Bindings::default(), - constraints: Constraints::default(), - may_be_unbound: self.bindings.may_be_unbound || b.bindings.may_be_unbound, + constraints: ConstraintsPerBinding::default(), + visibility_constraints: VisibilityConstraintPerBinding::default(), }, declarations: SymbolDeclarations { live_declarations: self.declarations.live_declarations.clone(), - may_be_undeclared: self.declarations.may_be_undeclared - || b.declarations.may_be_undeclared, + visibility_constraints: VisibilityConstraintPerBinding::default(), }, }; std::mem::swap(&mut a, self); - self.declarations - .live_declarations - .union(&b.declarations.live_declarations); let mut a_defs_iter = a.bindings.live_bindings.iter(); let mut b_defs_iter = b.bindings.live_bindings.iter(); let mut a_constraints_iter = a.bindings.constraints.into_iter(); let mut b_constraints_iter = b.bindings.constraints.into_iter(); + let mut a_vis_constraints_iter = a.bindings.visibility_constraints.into_iter(); + let mut b_vis_constraints_iter = b.bindings.visibility_constraints.into_iter(); let mut opt_a_def: Option = a_defs_iter.next(); let mut opt_b_def: Option = b_defs_iter.next(); @@ -261,7 +334,10 @@ impl SymbolState { // path is irrelevant. // Helper to push `def`, with constraints in `constraints_iter`, onto `self`. - let push = |def, constraints_iter: &mut ConstraintsIntoIterator, merged: &mut Self| { + let push = |def, + constraints_iter: &mut ConstraintsIntoIterator, + visibility_constraints_iter: &mut VisibilityConstraintsIntoIterator, + merged: &mut Self| { merged.bindings.live_bindings.insert(def); // SAFETY: we only ever create SymbolState with either no definitions and no constraint // bitsets (`::unbound`) or one definition and one constraint bitset (`::with`), and @@ -271,7 +347,14 @@ impl SymbolState { let constraints = constraints_iter .next() .expect("definitions and constraints length mismatch"); + let visibility_constraints = visibility_constraints_iter + .next() + .expect("definitions and visibility_constraints length mismatch"); merged.bindings.constraints.push(constraints); + merged + .bindings + .visibility_constraints + .push(visibility_constraints); }; loop { @@ -279,17 +362,32 @@ impl SymbolState { (Some(a_def), Some(b_def)) => match a_def.cmp(&b_def) { std::cmp::Ordering::Less => { // Next definition ID is only in `a`, push it to `self` and advance `a`. - push(a_def, &mut a_constraints_iter, self); + push( + a_def, + &mut a_constraints_iter, + &mut a_vis_constraints_iter, + self, + ); opt_a_def = a_defs_iter.next(); } std::cmp::Ordering::Greater => { // Next definition ID is only in `b`, push it to `self` and advance `b`. - push(b_def, &mut b_constraints_iter, self); + push( + b_def, + &mut b_constraints_iter, + &mut b_vis_constraints_iter, + self, + ); opt_b_def = b_defs_iter.next(); } std::cmp::Ordering::Equal => { // Next definition is in both; push to `self` and intersect constraints. - push(a_def, &mut b_constraints_iter, self); + push( + a_def, + &mut b_constraints_iter, + &mut b_vis_constraints_iter, + self, + ); // SAFETY: we only ever create SymbolState with either no definitions and // no constraint bitsets (`::unbound`) or one definition and one constraint // bitset (`::with`), and `::merge` always pushes one definition and one @@ -298,6 +396,7 @@ impl SymbolState { let a_constraints = a_constraints_iter .next() .expect("definitions and constraints length mismatch"); + // If the same definition is visible through both paths, any constraint // that applies on only one path is irrelevant to the resulting type from // unioning the two paths, so we intersect the constraints. @@ -306,23 +405,132 @@ impl SymbolState { .last_mut() .unwrap() .intersect(&a_constraints); + + // TODO: documentation + // SAFETY: See above + let a_vis_constraint = a_vis_constraints_iter + .next() + .expect("visibility_constraints length mismatch"); + let current = self.bindings.visibility_constraints.last_mut().unwrap(); + match ( + &all_visibility_constraints[*current], + &all_visibility_constraints[a_vis_constraint], + ) { + (_, VisibilityConstraintRef::Negated(id)) if current == id => { + *current = ScopedVisibilityConstraintId::from_u32(0); + } + + (VisibilityConstraintRef::Negated(id), _) + if *id == a_vis_constraint => + { + *current = ScopedVisibilityConstraintId::from_u32(0); + } + _ => { + let constraint_id = all_visibility_constraints + .push(VisibilityConstraintRef::Or(*current, a_vis_constraint)); + *current = constraint_id; + } + } + opt_a_def = a_defs_iter.next(); opt_b_def = b_defs_iter.next(); } }, (Some(a_def), None) => { // We've exhausted `b`, just push the def from `a` and move on to the next. - push(a_def, &mut a_constraints_iter, self); + push( + a_def, + &mut a_constraints_iter, + &mut a_vis_constraints_iter, + self, + ); opt_a_def = a_defs_iter.next(); } (None, Some(b_def)) => { // We've exhausted `a`, just push the def from `b` and move on to the next. - push(b_def, &mut b_constraints_iter, self); + push( + b_def, + &mut b_constraints_iter, + &mut b_vis_constraints_iter, + self, + ); opt_b_def = b_defs_iter.next(); } (None, None) => break, } } + + // Same as above, but for declarations. + let mut a_decls_iter = a.declarations.live_declarations.iter(); + let mut b_decls_iter = b.declarations.live_declarations.iter(); + let mut a_vis_constraints_iter = a.declarations.visibility_constraints.into_iter(); + let mut b_vis_constraints_iter = b.declarations.visibility_constraints.into_iter(); + + let mut opt_a_decl: Option = a_decls_iter.next(); + let mut opt_b_decl: Option = b_decls_iter.next(); + + let push = + |decl, conditions_iter: &mut VisibilityConstraintsIntoIterator, merged: &mut Self| { + merged.declarations.live_declarations.insert(decl); + let conditions = conditions_iter + .next() + .expect("declarations and visibility_constraints length mismatch"); + merged.declarations.visibility_constraints.push(conditions); + }; + + loop { + match (opt_a_decl, opt_b_decl) { + (Some(a_decl), Some(b_decl)) => match a_decl.cmp(&b_decl) { + std::cmp::Ordering::Less => { + push(a_decl, &mut a_vis_constraints_iter, self); + opt_a_decl = a_decls_iter.next(); + } + std::cmp::Ordering::Greater => { + push(b_decl, &mut b_vis_constraints_iter, self); + opt_b_decl = b_decls_iter.next(); + } + std::cmp::Ordering::Equal => { + push(a_decl, &mut b_vis_constraints_iter, self); + + let a_vis_constraint = a_vis_constraints_iter + .next() + .expect("declarations and visibility_constraints length mismatch"); + let current = self.declarations.visibility_constraints.last_mut().unwrap(); + match ( + &all_visibility_constraints[*current], + &all_visibility_constraints[a_vis_constraint], + ) { + (_, VisibilityConstraintRef::Negated(id)) if current == id => { + *current = ScopedVisibilityConstraintId::from_u32(0); + } + + (VisibilityConstraintRef::Negated(id), _) + if *id == a_vis_constraint => + { + *current = ScopedVisibilityConstraintId::from_u32(0); + } + _ => { + let constraint_id = all_visibility_constraints + .push(VisibilityConstraintRef::Or(*current, a_vis_constraint)); + *current = constraint_id; + } + } + + opt_a_decl = a_decls_iter.next(); + opt_b_decl = b_decls_iter.next(); + } + }, + (Some(a_decl), None) => { + push(a_decl, &mut a_vis_constraints_iter, self); + opt_a_decl = a_decls_iter.next(); + } + (None, Some(b_decl)) => { + push(b_decl, &mut b_vis_constraints_iter, self); + opt_b_decl = b_decls_iter.next(); + } + (None, None) => break, + } + } } pub(super) fn bindings(&self) -> &SymbolBindings { @@ -332,54 +540,58 @@ impl SymbolState { pub(super) fn declarations(&self) -> &SymbolDeclarations { &self.declarations } - - /// Could the symbol be unbound? - pub(super) fn may_be_unbound(&self) -> bool { - self.bindings.may_be_unbound() - } -} - -/// The default state of a symbol, if we've seen no definitions of it, is undefined (that is, -/// both unbound and undeclared). -impl Default for SymbolState { - fn default() -> Self { - SymbolState::undefined() - } } /// A single binding (as [`ScopedDefinitionId`]) with an iterator of its applicable /// [`ScopedConstraintId`]. #[derive(Debug)] -pub(super) struct BindingIdWithConstraints<'a> { +pub(super) struct BindingIdWithConstraints<'map, 'db> { pub(super) definition: ScopedDefinitionId, - pub(super) constraint_ids: ConstraintIdIterator<'a>, + pub(super) constraint_ids: ConstraintIdIterator<'map>, + pub(super) all_constraints: &'map IndexVec>, + pub(super) all_visibility_constraints: + &'map IndexVec, + pub(super) visibility_constraint: ScopedVisibilityConstraintId, } #[derive(Debug)] -pub(super) struct BindingIdWithConstraintsIterator<'a> { - definitions: BindingsIterator<'a>, - constraints: ConstraintsIterator<'a>, +pub(super) struct BindingIdWithConstraintsIterator<'map, 'db> { + all_constraints: &'map IndexVec>, + all_visibility_constraints: + &'map IndexVec, + definitions: BindingsIterator<'map>, + constraints: ConstraintsIterator<'map>, + visibility_constraints: VisibilityConstraintsIterator<'map>, } -impl<'a> Iterator for BindingIdWithConstraintsIterator<'a> { - type Item = BindingIdWithConstraints<'a>; +impl<'map, 'db> Iterator for BindingIdWithConstraintsIterator<'map, 'db> { + type Item = BindingIdWithConstraints<'map, 'db>; fn next(&mut self) -> Option { - match (self.definitions.next(), self.constraints.next()) { - (None, None) => None, - (Some(def), Some(constraints)) => Some(BindingIdWithConstraints { - definition: ScopedDefinitionId::from_u32(def), - constraint_ids: ConstraintIdIterator { - wrapped: constraints.iter(), - }, - }), + match ( + self.definitions.next(), + self.constraints.next(), + self.visibility_constraints.next(), + ) { + (None, None, None) => None, + (Some(def), Some(constraints), Some(visibility_constraint_id)) => { + Some(BindingIdWithConstraints { + definition: ScopedDefinitionId::from_u32(def), + constraint_ids: ConstraintIdIterator { + wrapped: constraints.iter(), + }, + all_constraints: self.all_constraints, + all_visibility_constraints: self.all_visibility_constraints, + visibility_constraint: *visibility_constraint_id, + }) + } // SAFETY: see above. _ => unreachable!("definitions and constraints length mismatch"), } } } -impl std::iter::FusedIterator for BindingIdWithConstraintsIterator<'_> {} +impl std::iter::FusedIterator for BindingIdWithConstraintsIterator<'_, '_> {} #[derive(Debug)] pub(super) struct ConstraintIdIterator<'a> { @@ -396,193 +608,215 @@ impl Iterator for ConstraintIdIterator<'_> { impl std::iter::FusedIterator for ConstraintIdIterator<'_> {} -#[derive(Debug)] -pub(super) struct DeclarationIdIterator<'a> { - inner: DeclarationsIterator<'a>, +pub(super) struct DeclarationIdIterator<'map, 'db> { + pub(crate) all_constraints: &'map IndexVec>, + pub(crate) all_visibility_constraints: + &'map IndexVec, + pub(crate) inner: DeclarationsIterator<'map>, + pub(crate) visibility_constraints: VisibilityConstraintsIterator<'map>, } -impl Iterator for DeclarationIdIterator<'_> { - type Item = ScopedDefinitionId; +impl<'map, 'db> Iterator for DeclarationIdIterator<'map, 'db> { + type Item = ( + ScopedDefinitionId, + &'map IndexVec>, + &'map IndexVec, + ScopedVisibilityConstraintId, + ); fn next(&mut self) -> Option { - self.inner.next().map(ScopedDefinitionId::from_u32) + match (self.inner.next(), self.visibility_constraints.next()) { + (None, None) => None, + (Some(declaration), Some(visibility_constraints_id)) => Some(( + ScopedDefinitionId::from_u32(declaration), + self.all_constraints, + self.all_visibility_constraints, + *visibility_constraints_id, + )), + // SAFETY: see above. + _ => unreachable!("declarations and visibility_constraints length mismatch"), + } } } -impl std::iter::FusedIterator for DeclarationIdIterator<'_> {} +impl std::iter::FusedIterator for DeclarationIdIterator<'_, '_> {} #[cfg(test)] mod tests { - use super::{ScopedConstraintId, ScopedDefinitionId, SymbolState}; - - fn assert_bindings(symbol: &SymbolState, may_be_unbound: bool, expected: &[&str]) { - assert_eq!(symbol.may_be_unbound(), may_be_unbound); - let actual = symbol - .bindings() - .iter() - .map(|def_id_with_constraints| { - format!( - "{}<{}>", - def_id_with_constraints.definition.as_u32(), - def_id_with_constraints - .constraint_ids - .map(ScopedConstraintId::as_u32) - .map(|idx| idx.to_string()) - .collect::>() - .join(", ") - ) - }) - .collect::>(); - assert_eq!(actual, expected); - } - - pub(crate) fn assert_declarations( - symbol: &SymbolState, - may_be_undeclared: bool, - expected: &[u32], - ) { - assert_eq!(symbol.declarations.may_be_undeclared(), may_be_undeclared); - let actual = symbol - .declarations() - .iter() - .map(ScopedDefinitionId::as_u32) - .collect::>(); - assert_eq!(actual, expected); - } - - #[test] - fn unbound() { - let sym = SymbolState::undefined(); - - assert_bindings(&sym, true, &[]); - } - - #[test] - fn with() { - let mut sym = SymbolState::undefined(); - sym.record_binding(ScopedDefinitionId::from_u32(0)); - - assert_bindings(&sym, false, &["0<>"]); - } - - #[test] - fn set_may_be_unbound() { - let mut sym = SymbolState::undefined(); - sym.record_binding(ScopedDefinitionId::from_u32(0)); - sym.set_may_be_unbound(); - - assert_bindings(&sym, true, &["0<>"]); - } + // use super::*; + + // #[track_caller] + // fn assert_bindings(symbol: &SymbolState, may_be_unbound: bool, expected: &[&str]) { + // assert_eq!(symbol.bindings.may_be_unbound, may_be_unbound); + // let mut actual = symbol + // .bindings() + // .iter() + // .map(|def_id_with_constraints| { + // format!( + // "{}<{}>", + // def_id_with_constraints.definition.as_u32(), + // def_id_with_constraints + // .constraint_ids + // .map(ScopedConstraintId::as_u32) + // .map(|idx| idx.to_string()) + // .collect::>() + // .join(", ") + // ) + // }) + // .collect::>(); + // actual.reverse(); + // assert_eq!(actual, expected); + // } + + // #[track_caller] + // pub(crate) fn assert_declarations( + // symbol: &SymbolState, + // may_be_undeclared: bool, + // expected: &[u32], + // ) { + // assert_eq!(symbol.declarations.may_be_undeclared(), may_be_undeclared); + // let mut actual = symbol + // .declarations() + // .iter() + // .map(|(d, _)| d.as_u32()) + // .collect::>(); + // actual.reverse(); + // assert_eq!(actual, expected); + // } + + // #[test] + // fn unbound() { + // let sym = SymbolState::undefined(); + + // assert_bindings(&sym, true, &[]); + // } + + // #[test] + // fn with() { + // let mut sym = SymbolState::undefined(); + // sym.record_binding(ScopedDefinitionId::from_u32(0)); + + // assert_bindings(&sym, false, &["0<>"]); + // } + + // #[test] + // fn set_may_be_unbound() { + // let mut sym = SymbolState::undefined(); + // sym.record_binding(ScopedDefinitionId::from_u32(0)); + // sym.set_may_be_unbound(); + + // assert_bindings(&sym, true, &["0<>"]); + // } + + // #[test] + // fn record_constraint() { + // let mut sym = SymbolState::undefined(); + // sym.record_binding(ScopedDefinitionId::from_u32(0)); + // sym.record_constraint(ScopedConstraintId::from_u32(0)); + + // assert_bindings(&sym, false, &["0<0>"]); + // } + + // #[test] + // fn merge() { + // // merging the same definition with the same constraint keeps the constraint + // let mut sym0a = SymbolState::undefined(); + // sym0a.record_binding(ScopedDefinitionId::from_u32(0)); + // sym0a.record_constraint(ScopedConstraintId::from_u32(0)); + + // let mut sym0b = SymbolState::undefined(); + // sym0b.record_binding(ScopedDefinitionId::from_u32(0)); + // sym0b.record_constraint(ScopedConstraintId::from_u32(0)); + + // sym0a.merge(sym0b); + // let mut sym0 = sym0a; + // assert_bindings(&sym0, false, &["0<0>"]); + + // // merging the same definition with differing constraints drops all constraints + // let mut sym1a = SymbolState::undefined(); + // sym1a.record_binding(ScopedDefinitionId::from_u32(1)); + // sym1a.record_constraint(ScopedConstraintId::from_u32(1)); + + // let mut sym1b = SymbolState::undefined(); + // sym1b.record_binding(ScopedDefinitionId::from_u32(1)); + // sym1b.record_constraint(ScopedConstraintId::from_u32(2)); + + // sym1a.merge(sym1b); + // let sym1 = sym1a; + // assert_bindings(&sym1, false, &["1<>"]); + + // // merging a constrained definition with unbound keeps both + // let mut sym2a = SymbolState::undefined(); + // sym2a.record_binding(ScopedDefinitionId::from_u32(2)); + // sym2a.record_constraint(ScopedConstraintId::from_u32(3)); + + // let sym2b = SymbolState::undefined(); + + // sym2a.merge(sym2b); + // let sym2 = sym2a; + // assert_bindings(&sym2, true, &["2<3>"]); + + // // merging different definitions keeps them each with their existing constraints + // sym0.merge(sym2); + // let sym = sym0; + // assert_bindings(&sym, true, &["0<0>", "2<3>"]); + // } + + // #[test] + // fn no_declaration() { + // let sym = SymbolState::undefined(); + + // assert_declarations(&sym, true, &[]); + // } + + // #[test] + // fn record_declaration() { + // let mut sym = SymbolState::undefined(); + // sym.record_declaration(ScopedDefinitionId::from_u32(1)); + + // assert_declarations(&sym, false, &[1]); + // } + + // #[test] + // fn record_declaration_override() { + // let mut sym = SymbolState::undefined(); + // sym.record_declaration(ScopedDefinitionId::from_u32(1)); + // sym.record_declaration(ScopedDefinitionId::from_u32(2)); - #[test] - fn record_constraint() { - let mut sym = SymbolState::undefined(); - sym.record_binding(ScopedDefinitionId::from_u32(0)); - sym.record_constraint(ScopedConstraintId::from_u32(0)); + // assert_declarations(&sym, false, &[2]); + // } - assert_bindings(&sym, false, &["0<0>"]); - } + // #[test] + // fn record_declaration_merge() { + // let mut sym = SymbolState::undefined(); + // sym.record_declaration(ScopedDefinitionId::from_u32(1)); - #[test] - fn merge() { - // merging the same definition with the same constraint keeps the constraint - let mut sym0a = SymbolState::undefined(); - sym0a.record_binding(ScopedDefinitionId::from_u32(0)); - sym0a.record_constraint(ScopedConstraintId::from_u32(0)); - - let mut sym0b = SymbolState::undefined(); - sym0b.record_binding(ScopedDefinitionId::from_u32(0)); - sym0b.record_constraint(ScopedConstraintId::from_u32(0)); - - sym0a.merge(sym0b); - let mut sym0 = sym0a; - assert_bindings(&sym0, false, &["0<0>"]); - - // merging the same definition with differing constraints drops all constraints - let mut sym1a = SymbolState::undefined(); - sym1a.record_binding(ScopedDefinitionId::from_u32(1)); - sym1a.record_constraint(ScopedConstraintId::from_u32(1)); - - let mut sym1b = SymbolState::undefined(); - sym1b.record_binding(ScopedDefinitionId::from_u32(1)); - sym1b.record_constraint(ScopedConstraintId::from_u32(2)); - - sym1a.merge(sym1b); - let sym1 = sym1a; - assert_bindings(&sym1, false, &["1<>"]); - - // merging a constrained definition with unbound keeps both - let mut sym2a = SymbolState::undefined(); - sym2a.record_binding(ScopedDefinitionId::from_u32(2)); - sym2a.record_constraint(ScopedConstraintId::from_u32(3)); - - let sym2b = SymbolState::undefined(); - - sym2a.merge(sym2b); - let sym2 = sym2a; - assert_bindings(&sym2, true, &["2<3>"]); - - // merging different definitions keeps them each with their existing constraints - sym0.merge(sym2); - let sym = sym0; - assert_bindings(&sym, true, &["0<0>", "2<3>"]); - } + // let mut sym2 = SymbolState::undefined(); + // sym2.record_declaration(ScopedDefinitionId::from_u32(2)); - #[test] - fn no_declaration() { - let sym = SymbolState::undefined(); + // sym.merge(sym2); - assert_declarations(&sym, true, &[]); - } + // assert_declarations(&sym, false, &[1, 2]); + // } - #[test] - fn record_declaration() { - let mut sym = SymbolState::undefined(); - sym.record_declaration(ScopedDefinitionId::from_u32(1)); + // #[test] + // fn record_declaration_merge_partial_undeclared() { + // let mut sym = SymbolState::undefined(); + // sym.record_declaration(ScopedDefinitionId::from_u32(1)); - assert_declarations(&sym, false, &[1]); - } + // let sym2 = SymbolState::undefined(); - #[test] - fn record_declaration_override() { - let mut sym = SymbolState::undefined(); - sym.record_declaration(ScopedDefinitionId::from_u32(1)); - sym.record_declaration(ScopedDefinitionId::from_u32(2)); + // sym.merge(sym2); - assert_declarations(&sym, false, &[2]); - } + // assert_declarations(&sym, true, &[1]); + // } - #[test] - fn record_declaration_merge() { - let mut sym = SymbolState::undefined(); - sym.record_declaration(ScopedDefinitionId::from_u32(1)); + // #[test] + // fn set_may_be_undeclared() { + // let mut sym = SymbolState::undefined(); + // sym.record_declaration(ScopedDefinitionId::from_u32(0)); + // sym.set_may_be_undeclared(); - let mut sym2 = SymbolState::undefined(); - sym2.record_declaration(ScopedDefinitionId::from_u32(2)); - - sym.merge(sym2); - - assert_declarations(&sym, false, &[1, 2]); - } - - #[test] - fn record_declaration_merge_partial_undeclared() { - let mut sym = SymbolState::undefined(); - sym.record_declaration(ScopedDefinitionId::from_u32(1)); - - let sym2 = SymbolState::undefined(); - - sym.merge(sym2); - - assert_declarations(&sym, true, &[1]); - } - - #[test] - fn set_may_be_undeclared() { - let mut sym = SymbolState::undefined(); - sym.record_declaration(ScopedDefinitionId::from_u32(0)); - sym.set_may_be_undeclared(); - - assert_declarations(&sym, true, &[0]); - } + // assert_declarations(&sym, true, &[0]); + // } } diff --git a/crates/red_knot_python_semantic/src/semantic_index/visibility_constraint.rs b/crates/red_knot_python_semantic/src/semantic_index/visibility_constraint.rs new file mode 100644 index 0000000000000..f2769cff77e42 --- /dev/null +++ b/crates/red_knot_python_semantic/src/semantic_index/visibility_constraint.rs @@ -0,0 +1,34 @@ +use crate::semantic_index::use_def::{ScopedConstraintId, ScopedVisibilityConstraintId}; + +/// TODO +/// +/// Used to represent active branching conditions that apply to a particular definition. +/// A definition can either be conditional on a specific constraint from a `if`, `elif`, +/// `while` statement, an `if`-expression, or a Boolean expression. Or it can be marked +/// as 'ambiguous' if it occurred in a control-flow path that is not conditional on any +/// specific expression that can be statically analyzed (`for` loop, `try` ... `except`). +/// +/// +/// For example: +/// ```py +/// a = 1 # no visibility constraints +/// +/// if test1: +/// b = 1 # Constraint(test1) +/// +/// if test2: +/// c = 1 # Constraint(test1), Constraint(test2) +/// +/// for _ in range(10): +/// d = 1 # Constraint(test1), Ambiguous +/// else: +/// d = 1 # Constraint(~test1) +/// ``` +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) enum VisibilityConstraintRef { + None, + Single(ScopedConstraintId), + Negated(ScopedVisibilityConstraintId), + And(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), + Or(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), +} diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index d655e18d8f848..6bd1f35727f29 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -33,6 +33,7 @@ use crate::types::diagnostic::INVALID_TYPE_FORM; use crate::types::mro::{Mro, MroError, MroIterator}; use crate::types::narrow::narrowing_constraint; use crate::{Db, FxOrderSet, Module, Program, PythonVersion}; +pub(crate) use static_truthiness::static_visibility; mod builder; mod call; @@ -44,6 +45,7 @@ mod infer; mod mro; mod narrow; mod signatures; +mod static_truthiness; mod string_annotation; mod unpacker; @@ -69,61 +71,64 @@ pub fn check_types(db: &dyn Db, file: File) -> TypeCheckDiagnostics { /// Infer the public type of a symbol (its type as seen from outside its scope). fn symbol_by_id<'db>(db: &'db dyn Db, scope: ScopeId<'db>, symbol: ScopedSymbolId) -> Symbol<'db> { - let _span = tracing::trace_span!("symbol_by_id", ?symbol).entered(); - let use_def = use_def_map(db, scope); // If the symbol is declared, the public type is based on declarations; otherwise, it's based // on inference from bindings. - if use_def.has_public_declarations(symbol) { - let declarations = use_def.public_declarations(symbol); - // If the symbol is undeclared in some paths, include the inferred type in the public type. - let undeclared_ty = if declarations.may_be_undeclared() { - Some( - bindings_ty(db, use_def.public_bindings(symbol)) - .map(|bindings_ty| Symbol::Type(bindings_ty, use_def.public_boundness(symbol))) - .unwrap_or(Symbol::Unbound), - ) - } else { - None - }; - // Intentionally ignore conflicting declared types; that's not our problem, it's the - // problem of the module we are importing from. - - // TODO: Our handling of boundness currently only depends on bindings, and ignores - // declarations. This is inconsistent, since we only look at bindings if the symbol - // may be undeclared. Consider the following example: - // ```py - // x: int - // - // if flag: - // y: int - // else - // y = 3 - // ``` - // If we import from this module, we will currently report `x` as a definitely-bound - // symbol (even though it has no bindings at all!) but report `y` as possibly-unbound - // (even though every path has either a binding or a declaration for it.) - - match undeclared_ty { - Some(Symbol::Type(ty, boundness)) => Symbol::Type( - declarations_ty(db, declarations, Some(ty)).unwrap_or_else(|(ty, _)| ty), - boundness, - ), - None | Some(Symbol::Unbound) => Symbol::Type( - declarations_ty(db, declarations, None).unwrap_or_else(|(ty, _)| ty), - Boundness::Bound, - ), + // let declaredness = use_def.public_declarations(symbol).declaredness(db); + + let declarations = use_def.public_declarations(symbol); + let declared_ty = declarations_ty(db, declarations); + + let bindings = use_def.public_bindings(symbol); // TODO: short circuit if declaredness is Bound + let inferred_ty = bindings_ty(db, bindings); + + match declared_ty { + // Symbol is declared, trust the declared type + Ok(symbol @ Symbol::Type(_, Boundness::Bound)) => symbol, + // Symbol is possibly declared + Ok(Symbol::Type(declared_ty, Boundness::PossiblyUnbound)) => { + match inferred_ty { + // Symbol is possibly undeclared and definitely unbound + Symbol::Unbound => Symbol::Type(declared_ty, Boundness::Bound), + // Symbol is possibly undeclared and possibly bound + inferred @ Symbol::Type(_, Boundness::Bound) => inferred, + // Symbol is possibly undeclared and possibly unbound + Symbol::Type(inferred_ty, Boundness::PossiblyUnbound) => Symbol::Type( + UnionType::from_elements(db, [declared_ty, inferred_ty].iter().copied()), + Boundness::PossiblyUnbound, + ), + } + } + // Symbol is undeclared + Ok(Symbol::Unbound) => inferred_ty, + // Symbol is possibly undeclared + Err((declared_ty, _)) => { + // TODO: conflicting declarations error + declared_ty.into() } - } else { - bindings_ty(db, use_def.public_bindings(symbol)) - .map(|bindings_ty| Symbol::Type(bindings_ty, use_def.public_boundness(symbol))) - .unwrap_or(Symbol::Unbound) } + + // TODO (ticket: https://github.com/astral-sh/ruff/issues/14297) Our handling of boundness + // currently only depends on bindings, and ignores declarations. This is inconsistent, since + // we only look at bindings if the symbol may be undeclared. Consider the following example: + // ```py + // x: int + // + // if flag: + // y: int + // else + // y = 3 + // ``` + // If we import from this module, we will currently report `x` as a definitely-bound symbol + // (even though it has no bindings at all!) but report `y` as possibly-unbound (even though + // every path has either a binding or a declaration for it.) } /// Shorthand for `symbol_by_id` that takes a symbol name instead of an ID. fn symbol<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str) -> Symbol<'db> { + let _span = tracing::trace_span!("symbol", ?name).entered(); + // We don't need to check for `typing_extensions` here, because `typing_extensions.TYPE_CHECKING` // is just a re-export of `typing.TYPE_CHECKING`. if name == "TYPE_CHECKING" @@ -247,46 +252,81 @@ fn definition_expression_ty<'db>( fn bindings_ty<'db>( db: &'db dyn Db, bindings_with_constraints: BindingWithConstraintsIterator<'_, 'db>, -) -> Option> { - let mut def_types = bindings_with_constraints.map( - |BindingWithConstraints { - binding, - constraints, - }| { - let mut constraint_tys = constraints - .filter_map(|constraint| narrowing_constraint(db, constraint, binding)) - .peekable(); - - let binding_ty = binding_ty(db, binding); - if constraint_tys.peek().is_some() { - constraint_tys - .fold( - IntersectionBuilder::new(db).add_positive(binding_ty), - IntersectionBuilder::add_positive, - ) - .build() - } else { - binding_ty +) -> Symbol<'db> { + let mut is_unbound_visible = false; + let mut types = vec![]; + + for BindingWithConstraints { + binding, + constraints, + all_constraints, + all_visibility_constraints, + visibility_constraint, + } in bindings_with_constraints + { + let static_visibility = static_visibility( + db, + all_constraints, + all_visibility_constraints, + visibility_constraint, + ); + + let Some(binding) = binding else { + if !static_visibility.is_always_false() { + // TODO: also distinguish between always-true and ambiguous? + is_unbound_visible = true; } - }, - ); + continue; + }; - if let Some(first) = def_types.next() { - if let Some(second) = def_types.next() { - Some(UnionType::from_elements( - db, - [first, second].into_iter().chain(def_types), - )) + if static_visibility.is_always_false() { + continue; + } + + let mut constraint_tys = constraints + .filter_map(|constraint| narrowing_constraint(db, constraint, binding)) + .peekable(); + + let binding_ty = binding_ty(db, binding); + let ty = if constraint_tys.peek().is_some() { + let intersection_ty = constraint_tys + .fold( + IntersectionBuilder::new(db).add_positive(binding_ty), + IntersectionBuilder::add_positive, + ) + .build(); + intersection_ty + } else { + binding_ty + }; + + types.push(ty); + } + + let mut types = types.into_iter(); + + if let Some(first) = types.next() { + let boundness = if is_unbound_visible { + Boundness::PossiblyUnbound } else { - Some(first) + Boundness::Bound + }; + + if let Some(second) = types.next() { + Symbol::Type( + UnionType::from_elements(db, [first, second].into_iter().chain(types)), + boundness, + ) + } else { + Symbol::Type(first, boundness) } } else { - None + Symbol::Unbound } } /// The result of looking up a declared type from declarations; see [`declarations_ty`]. -type DeclaredTypeResult<'db> = Result, (Type<'db>, Box<[Type<'db>]>)>; +type DeclaredTypeResult<'db> = Result, (Type<'db>, Box<[Type<'db>]>)>; /// Build a declared type from a [`DeclarationsIterator`]. /// @@ -304,40 +344,67 @@ type DeclaredTypeResult<'db> = Result, (Type<'db>, Box<[Type<'db>]>)>; fn declarations_ty<'db>( db: &'db dyn Db, declarations: DeclarationsIterator<'_, 'db>, - undeclared_ty: Option>, ) -> DeclaredTypeResult<'db> { - let mut declaration_types = declarations.map(|declaration| declaration_ty(db, declaration)); + let mut is_unbound_visible = false; + let mut types = vec![]; - let Some(first) = declaration_types.next() else { - if let Some(undeclared_ty) = undeclared_ty { - // Short-circuit to return the undeclared type if there are no declarations. - return Ok(undeclared_ty); - } - panic!("declarations_ty must not be called with zero declarations and no undeclared_ty"); - }; + for (declaration, all_constraints, all_visibility_constraints, visibility_constraint) in + declarations + { + let static_visibility = static_visibility( + db, + all_constraints, + all_visibility_constraints, + visibility_constraint, + ); + + let Some(declaration) = declaration else { + if !static_visibility.is_always_false() { + is_unbound_visible = true; + } + continue; + }; - let mut conflicting: Vec> = vec![]; - let mut builder = UnionBuilder::new(db).add(first); - for other in declaration_types { - if !first.is_equivalent_to(db, other) { - conflicting.push(other); + if static_visibility.is_always_false() { + continue; } - builder = builder.add(other); - } - // Avoid considering the undeclared type for the conflicting declaration diagnostics. It - // should still be part of the declared type. - if let Some(undeclared_ty) = undeclared_ty { - builder = builder.add(undeclared_ty); + + let ty = declaration_ty(db, declaration); + types.push(ty); } - let declared_ty = builder.build(); - if conflicting.is_empty() { - Ok(declared_ty) + let mut types = types.into_iter(); + + if let Some(first) = types.next() { + let mut conflicting: Vec> = vec![]; + let declared_ty = if let Some(second) = types.next() { + let mut builder = UnionBuilder::new(db).add(first); + for other in [second].into_iter().chain(types) { + if !first.is_equivalent_to(db, other) { + conflicting.push(other); + } + builder = builder.add(other); + } + builder.build() + } else { + first + }; + if conflicting.is_empty() { + let boundness = if is_unbound_visible { + Boundness::PossiblyUnbound + } else { + Boundness::Bound + }; + + Ok(Symbol::Type(declared_ty, boundness)) + } else { + Err(( + declared_ty, + [first].into_iter().chain(conflicting).collect(), + )) + } } else { - Err(( - declared_ty, - [first].into_iter().chain(conflicting).collect(), - )) + Ok(Symbol::Unbound) } } @@ -1580,7 +1647,7 @@ impl<'db> Type<'db> { /// /// This is used to determine the value that would be returned /// when `bool(x)` is called on an object `x`. - fn bool(&self, db: &'db dyn Db) -> Truthiness { + pub(crate) fn bool(&self, db: &'db dyn Db) -> Truthiness { match self { Type::Any | Type::Todo(_) | Type::Never | Type::Unknown => Truthiness::Ambiguous, Type::FunctionLiteral(_) => Truthiness::AlwaysTrue, @@ -2007,7 +2074,7 @@ impl<'db> Type<'db> { /// but it's a useful fallback for us in order to infer `Literal` types from `sys.version_info` comparisons. fn version_info_tuple(db: &'db dyn Db) -> Self { let python_version = Program::get(db).python_version(db); - let int_instance_ty = KnownClass::Int.to_instance(db); + let int_instance_ty = Type::Unknown; // TODO: just grab this type from typeshed (it's a `sys._ReleaseLevel` type alias there) let release_level_ty = { @@ -2859,11 +2926,15 @@ pub enum Truthiness { } impl Truthiness { - const fn is_ambiguous(self) -> bool { + pub(crate) const fn is_ambiguous(self) -> bool { matches!(self, Truthiness::Ambiguous) } - const fn negate(self) -> Self { + pub(crate) const fn is_always_false(self) -> bool { + matches!(self, Truthiness::AlwaysFalse) + } + + pub(crate) const fn negate(self) -> Self { match self { Self::AlwaysTrue => Self::AlwaysFalse, Self::AlwaysFalse => Self::AlwaysTrue, @@ -2871,6 +2942,14 @@ impl Truthiness { } } + pub(crate) const fn negate_if(self, condition: bool) -> Self { + if condition { + self.negate() + } else { + self + } + } + fn into_type(self, db: &dyn Db) -> Type { match self { Self::AlwaysTrue => Type::BooleanLiteral(true), diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 084451ee50d1d..527e05712462e 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -164,12 +164,28 @@ pub(crate) fn infer_deferred_types<'db>( TypeInferenceBuilder::new(db, InferenceRegion::Deferred(definition), index).finish() } +/// TODO: get rid of this? if not, at least build a cycle-recovery-variant of [`infer_expression_types`], +/// which is only used in static visibility analysis. +pub(crate) fn infer_expression_types_cycle_recovery<'db>( + db: &'db dyn Db, + _cycle: &salsa::Cycle, + expr: Expression<'db>, +) -> TypeInference<'db> { + tracing::trace!("infer_expression_types_cycle_recovery"); + let mut inference = TypeInference::empty(expr.scope(db)); + inference.expressions.insert( + expr.node_ref(db).scoped_expression_id(db, expr.scope(db)), + Type::Unknown, + ); + inference +} + /// Infer all types for an [`Expression`] (including sub-expressions). /// Use rarely; only for cases where we'd otherwise risk double-inferring an expression: RHS of an /// assignment, which might be unpacking/multi-target and thus part of multiple definitions, or a /// type narrowing guard expression (e.g. if statement test node). #[allow(unused)] -#[salsa::tracked(return_ref)] +#[salsa::tracked(return_ref, recovery_fn=infer_expression_types_cycle_recovery)] pub(crate) fn infer_expression_types<'db>( db: &'db dyn Db, expression: Expression<'db>, @@ -824,14 +840,15 @@ impl<'db> TypeInferenceBuilder<'db> { debug_assert!(binding.is_binding(self.db())); let use_def = self.index.use_def_map(binding.file_scope(self.db())); let declarations = use_def.declarations_at_binding(binding); - let undeclared_ty = if declarations.may_be_undeclared() { - Some(Type::Unknown) - } else { - None - }; + // let undeclared_ty = if declarations.clone().may_be_undeclared(self.db) { + // Symbol::Type(Type::Unknown, Boundness::Bound) + // } else { + // Symbol::Unbound + // }; let mut bound_ty = ty; - let declared_ty = declarations_ty(self.db(), declarations, undeclared_ty).unwrap_or_else( - |(ty, conflicting)| { + let declared_ty = declarations_ty(self.db(), declarations) + .map(|s| s.ignore_possibly_unbound().unwrap_or(Type::Unknown)) + .unwrap_or_else(|(ty, conflicting)| { // TODO point out the conflicting declarations in the diagnostic? let symbol_table = self.index.symbol_table(binding.file_scope(self.db())); let symbol_name = symbol_table.symbol(binding.symbol(self.db())).name(); @@ -844,8 +861,7 @@ impl<'db> TypeInferenceBuilder<'db> { ), ); ty - }, - ); + }); if !bound_ty.is_assignable_to(self.db(), declared_ty) { report_invalid_assignment(&self.context, node, declared_ty, bound_ty); // allow declarations to override inference in case of invalid assignment @@ -860,7 +876,9 @@ impl<'db> TypeInferenceBuilder<'db> { let use_def = self.index.use_def_map(declaration.file_scope(self.db())); let prior_bindings = use_def.bindings_at_declaration(declaration); // unbound_ty is Never because for this check we don't care about unbound - let inferred_ty = bindings_ty(self.db(), prior_bindings).unwrap_or(Type::Never); + let inferred_ty = bindings_ty(self.db(), prior_bindings) + .ignore_possibly_unbound() + .unwrap_or(Type::Never); let ty = if inferred_ty.is_assignable_to(self.db(), ty) { ty } else { @@ -1764,13 +1782,24 @@ impl<'db> TypeInferenceBuilder<'db> { fn infer_match_pattern(&mut self, pattern: &ast::Pattern) { // TODO(dhruvmanila): Add a Salsa query for inferring pattern types and matching against // the subject expression: https://github.com/astral-sh/ruff/pull/13147#discussion_r1739424510 + match pattern { + ast::Pattern::MatchValue(match_value) => { + self.infer_standalone_expression(&match_value.value); + } + _ => { + self.infer_match_pattern_impl(pattern); + } + } + } + + fn infer_match_pattern_impl(&mut self, pattern: &ast::Pattern) { match pattern { ast::Pattern::MatchValue(match_value) => { self.infer_expression(&match_value.value); } ast::Pattern::MatchSequence(match_sequence) => { for pattern in &match_sequence.patterns { - self.infer_match_pattern(pattern); + self.infer_match_pattern_impl(pattern); } } ast::Pattern::MatchMapping(match_mapping) => { @@ -1784,7 +1813,7 @@ impl<'db> TypeInferenceBuilder<'db> { self.infer_expression(key); } for pattern in patterns { - self.infer_match_pattern(pattern); + self.infer_match_pattern_impl(pattern); } } ast::Pattern::MatchClass(match_class) => { @@ -1794,21 +1823,21 @@ impl<'db> TypeInferenceBuilder<'db> { arguments, } = match_class; for pattern in &arguments.patterns { - self.infer_match_pattern(pattern); + self.infer_match_pattern_impl(pattern); } for keyword in &arguments.keywords { - self.infer_match_pattern(&keyword.pattern); + self.infer_match_pattern_impl(&keyword.pattern); } self.infer_expression(cls); } ast::Pattern::MatchAs(match_as) => { if let Some(pattern) = &match_as.pattern { - self.infer_match_pattern(pattern); + self.infer_match_pattern_impl(pattern); } } ast::Pattern::MatchOr(match_or) => { for pattern in &match_or.patterns { - self.infer_match_pattern(pattern); + self.infer_match_pattern_impl(pattern); } } ast::Pattern::MatchStar(_) | ast::Pattern::MatchSingleton(_) => {} @@ -3094,28 +3123,25 @@ impl<'db> TypeInferenceBuilder<'db> { let use_def = self.index.use_def_map(file_scope_id); // If we're inferring types of deferred expressions, always treat them as public symbols - let (bindings_ty, boundness) = if self.is_deferred() { + let bindings_ty = if self.is_deferred() { if let Some(symbol) = self.index.symbol_table(file_scope_id).symbol_id_by_name(id) { - ( - bindings_ty(self.db(), use_def.public_bindings(symbol)), - use_def.public_boundness(symbol), - ) + bindings_ty(self.db(), use_def.public_bindings(symbol)) } else { assert!( self.deferred_state.in_string_annotation(), "Expected the symbol table to create a symbol for every Name node" ); - (None, Boundness::PossiblyUnbound) + // (None, Some(Boundness::PossiblyUnbound)) + Symbol::Type(Type::Unknown, Boundness::PossiblyUnbound) } } else { let use_id = name.scoped_use_id(self.db(), self.scope()); - ( - bindings_ty(self.db(), use_def.bindings_at_use(use_id)), - use_def.use_boundness(use_id), - ) + bindings_ty(self.db(), use_def.bindings_at_use(use_id)) }; - if boundness == Boundness::PossiblyUnbound { + if let Symbol::Type(ty, Boundness::Bound) = bindings_ty { + ty + } else { match self.lookup_name(name) { Symbol::Type(looked_up_ty, looked_up_boundness) => { if looked_up_boundness == Boundness::PossiblyUnbound { @@ -3123,20 +3149,22 @@ impl<'db> TypeInferenceBuilder<'db> { } bindings_ty + .ignore_possibly_unbound() .map(|ty| UnionType::from_elements(self.db(), [ty, looked_up_ty])) .unwrap_or(looked_up_ty) } - Symbol::Unbound => { - if bindings_ty.is_some() { + Symbol::Unbound => match bindings_ty { + Symbol::Type(ty, Boundness::PossiblyUnbound) => { report_possibly_unresolved_reference(&self.context, name); - } else { + ty + } + Symbol::Unbound => { report_unresolved_reference(&self.context, name); + Type::Unknown } - bindings_ty.unwrap_or(Type::Unknown) - } + Symbol::Type(_, Boundness::Bound) => unreachable!("Handled above"), + }, } - } else { - bindings_ty.unwrap_or(Type::Unknown) } } @@ -6358,9 +6386,10 @@ mod tests { let scope = global_scope(db, file); use_def_map(db, scope) .public_bindings(symbol_table(db, scope).symbol_id_by_name(name).unwrap()) - .next() + .map(|b| b.binding) + .find(Option::is_some) + .unwrap() .unwrap() - .binding } #[test] diff --git a/crates/red_knot_python_semantic/src/types/narrow.rs b/crates/red_knot_python_semantic/src/types/narrow.rs index 53476cdbb4949..5d0c2f5136bce 100644 --- a/crates/red_knot_python_semantic/src/types/narrow.rs +++ b/crates/red_knot_python_semantic/src/types/narrow.rs @@ -1,5 +1,7 @@ use crate::semantic_index::ast_ids::HasScopedExpressionId; -use crate::semantic_index::constraint::{Constraint, ConstraintNode, PatternConstraint}; +use crate::semantic_index::constraint::{ + Constraint, ConstraintNode, PatternConstraint, PatternConstraintKind, +}; use crate::semantic_index::definition::Definition; use crate::semantic_index::expression::Expression; use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId, SymbolTable}; @@ -216,31 +218,12 @@ impl<'db> NarrowingConstraintsBuilder<'db> { ) -> Option> { let subject = pattern.subject(self.db); - match pattern.pattern(self.db).node() { - ast::Pattern::MatchValue(_) => { - None // TODO - } - ast::Pattern::MatchSingleton(singleton_pattern) => { - self.evaluate_match_pattern_singleton(subject, singleton_pattern) - } - ast::Pattern::MatchSequence(_) => { - None // TODO - } - ast::Pattern::MatchMapping(_) => { - None // TODO - } - ast::Pattern::MatchClass(_) => { - None // TODO - } - ast::Pattern::MatchStar(_) => { - None // TODO - } - ast::Pattern::MatchAs(_) => { - None // TODO - } - ast::Pattern::MatchOr(_) => { - None // TODO + match pattern.kind(self.db) { + PatternConstraintKind::Singleton(singleton) => { + self.evaluate_match_pattern_singleton(*subject, *singleton) } + // TODO: support more pattern kinds + PatternConstraintKind::Value(_) | PatternConstraintKind::Unsupported => None, } } @@ -483,14 +466,14 @@ impl<'db> NarrowingConstraintsBuilder<'db> { fn evaluate_match_pattern_singleton( &mut self, - subject: &ast::Expr, - pattern: &ast::PatternMatchSingleton, + subject: Expression<'db>, + singleton: ast::Singleton, ) -> Option> { - if let Some(ast::ExprName { id, .. }) = subject.as_name_expr() { + if let Some(ast::ExprName { id, .. }) = subject.node_ref(self.db).as_name_expr() { // SAFETY: we should always have a symbol for every Name node. let symbol = self.symbols().symbol_id_by_name(id).unwrap(); - let ty = match pattern.value { + let ty = match singleton { ast::Singleton::None => Type::none(self.db), ast::Singleton::True => Type::BooleanLiteral(true), ast::Singleton::False => Type::BooleanLiteral(false), diff --git a/crates/red_knot_python_semantic/src/types/static_truthiness.rs b/crates/red_knot_python_semantic/src/types/static_truthiness.rs new file mode 100644 index 0000000000000..b4146af20d410 --- /dev/null +++ b/crates/red_knot_python_semantic/src/types/static_truthiness.rs @@ -0,0 +1,94 @@ +use ruff_index::IndexVec; + +use crate::semantic_index::{ + ast_ids::HasScopedExpressionId, + constraint::{Constraint, ConstraintNode, PatternConstraintKind}, + visibility_constraint::VisibilityConstraintRef, + ScopedConstraintId, ScopedVisibilityConstraintId, +}; +use crate::types::{infer_expression_types, Truthiness}; +use crate::Db; + +/// Analyze the statically known visibility for a given visibility constraint. +pub(crate) fn static_visibility<'db>( + db: &'db dyn Db, + all_constraints: &IndexVec>, + all_visibility_constraints: &IndexVec, + visibility_constraint_id: ScopedVisibilityConstraintId, +) -> Truthiness { + let visibility_constraint = &all_visibility_constraints[visibility_constraint_id]; + match visibility_constraint { + VisibilityConstraintRef::Single(id) => { + let constraint = &all_constraints[*id]; + + match constraint.node { + ConstraintNode::Expression(test_expr) => { + let inference = infer_expression_types(db, test_expr); + let scope = test_expr.scope(db); + let ty = inference + .expression_ty(test_expr.node_ref(db).scoped_expression_id(db, scope)); + + ty.bool(db).negate_if(!constraint.is_positive) + } + ConstraintNode::Pattern(inner) => match inner.kind(db) { + PatternConstraintKind::Value(value) => { + let subject_expression = inner.subject(db); + let inference = infer_expression_types(db, *subject_expression); + let scope = subject_expression.scope(db); + let subject_ty = inference.expression_ty( + subject_expression + .node_ref(db) + .scoped_expression_id(db, scope), + ); + + let inference = infer_expression_types(db, *value); + let scope = value.scope(db); + let value_ty = inference + .expression_ty(value.node_ref(db).scoped_expression_id(db, scope)); + + if subject_ty.is_single_valued(db) { + Truthiness::from(subject_ty.is_equivalent_to(db, value_ty)) + } else { + Truthiness::Ambiguous + } + } + PatternConstraintKind::Singleton(_) | PatternConstraintKind::Unsupported => { + Truthiness::Ambiguous + } + }, + } + } + VisibilityConstraintRef::Negated(visibility_constraint_id) => static_visibility( + db, + all_constraints, + all_visibility_constraints, + *visibility_constraint_id, + ) + .negate(), + VisibilityConstraintRef::None => Truthiness::AlwaysTrue, + VisibilityConstraintRef::And(lhs_id, rhs_id) => { + let lhs = static_visibility(db, all_constraints, all_visibility_constraints, *lhs_id); + let rhs = static_visibility(db, all_constraints, all_visibility_constraints, *rhs_id); + + if lhs == Truthiness::AlwaysFalse || rhs == Truthiness::AlwaysFalse { + Truthiness::AlwaysFalse + } else if lhs == Truthiness::AlwaysTrue && rhs == Truthiness::AlwaysTrue { + Truthiness::AlwaysTrue + } else { + Truthiness::Ambiguous + } + } + VisibilityConstraintRef::Or(lhs_id, rhs_id) => { + let lhs = static_visibility(db, all_constraints, all_visibility_constraints, *lhs_id); + let rhs = static_visibility(db, all_constraints, all_visibility_constraints, *rhs_id); + + if lhs == Truthiness::AlwaysFalse && rhs == Truthiness::AlwaysFalse { + Truthiness::AlwaysFalse + } else if lhs == Truthiness::AlwaysTrue || rhs == Truthiness::AlwaysTrue { + Truthiness::AlwaysTrue + } else { + Truthiness::Ambiguous + } + } + } +} From 34be5b6b0017029258c823dd0f0a9d1688abad20 Mon Sep 17 00:00:00 2001 From: David Peter Date: Tue, 17 Dec 2024 10:31:06 +0100 Subject: [PATCH 02/65] Temporarily patch typeshed to avoid cycles --- .../src/types/infer.rs | 18 +----------------- .../vendor/typeshed/stdlib/builtins.pyi | 4 ++-- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 527e05712462e..f7ee8577d2267 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -164,28 +164,12 @@ pub(crate) fn infer_deferred_types<'db>( TypeInferenceBuilder::new(db, InferenceRegion::Deferred(definition), index).finish() } -/// TODO: get rid of this? if not, at least build a cycle-recovery-variant of [`infer_expression_types`], -/// which is only used in static visibility analysis. -pub(crate) fn infer_expression_types_cycle_recovery<'db>( - db: &'db dyn Db, - _cycle: &salsa::Cycle, - expr: Expression<'db>, -) -> TypeInference<'db> { - tracing::trace!("infer_expression_types_cycle_recovery"); - let mut inference = TypeInference::empty(expr.scope(db)); - inference.expressions.insert( - expr.node_ref(db).scoped_expression_id(db, expr.scope(db)), - Type::Unknown, - ); - inference -} - /// Infer all types for an [`Expression`] (including sub-expressions). /// Use rarely; only for cases where we'd otherwise risk double-inferring an expression: RHS of an /// assignment, which might be unpacking/multi-target and thus part of multiple definitions, or a /// type narrowing guard expression (e.g. if statement test node). #[allow(unused)] -#[salsa::tracked(return_ref, recovery_fn=infer_expression_types_cycle_recovery)] +#[salsa::tracked(return_ref)] pub(crate) fn infer_expression_types<'db>( db: &'db dyn Db, expression: Expression<'db>, diff --git a/crates/red_knot_vendored/vendor/typeshed/stdlib/builtins.pyi b/crates/red_knot_vendored/vendor/typeshed/stdlib/builtins.pyi index ad10ba9dff4cf..d75d17cbd209a 100644 --- a/crates/red_knot_vendored/vendor/typeshed/stdlib/builtins.pyi +++ b/crates/red_knot_vendored/vendor/typeshed/stdlib/builtins.pyi @@ -1931,8 +1931,8 @@ class OSError(Exception): EnvironmentError = OSError IOError = OSError -if sys.platform == "win32": - WindowsError = OSError +# if sys.platform == "win32": +# WindowsError = OSError class ArithmeticError(Exception): ... class AssertionError(Exception): ... From 7b79a56ea6cc5b92ecb748aa1818bf1b26ba2db6 Mon Sep 17 00:00:00 2001 From: David Peter Date: Tue, 17 Dec 2024 10:59:05 +0100 Subject: [PATCH 03/65] Another patch to fix the sys.version_info tests --- crates/red_knot_python_semantic/src/types.rs | 2 +- .../vendor/typeshed/stdlib/builtins.pyi | 54 +++++++++---------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 6bd1f35727f29..ee5d2c418c5a0 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2074,7 +2074,7 @@ impl<'db> Type<'db> { /// but it's a useful fallback for us in order to infer `Literal` types from `sys.version_info` comparisons. fn version_info_tuple(db: &'db dyn Db) -> Self { let python_version = Program::get(db).python_version(db); - let int_instance_ty = Type::Unknown; + let int_instance_ty = KnownClass::Int.to_instance(db); // TODO: just grab this type from typeshed (it's a `sys._ReleaseLevel` type alias there) let release_level_ty = { diff --git a/crates/red_knot_vendored/vendor/typeshed/stdlib/builtins.pyi b/crates/red_knot_vendored/vendor/typeshed/stdlib/builtins.pyi index d75d17cbd209a..037d6d0c64b20 100644 --- a/crates/red_knot_vendored/vendor/typeshed/stdlib/builtins.pyi +++ b/crates/red_knot_vendored/vendor/typeshed/stdlib/builtins.pyi @@ -1392,33 +1392,33 @@ else: ) -> Any: ... # Comment above regarding `eval` applies to `exec` as well -if sys.version_info >= (3, 13): - def exec( - source: str | ReadableBuffer | CodeType, - /, - globals: dict[str, Any] | None = None, - locals: Mapping[str, object] | None = None, - *, - closure: tuple[CellType, ...] | None = None, - ) -> None: ... - -elif sys.version_info >= (3, 11): - def exec( - source: str | ReadableBuffer | CodeType, - globals: dict[str, Any] | None = None, - locals: Mapping[str, object] | None = None, - /, - *, - closure: tuple[CellType, ...] | None = None, - ) -> None: ... - -else: - def exec( - source: str | ReadableBuffer | CodeType, - globals: dict[str, Any] | None = None, - locals: Mapping[str, object] | None = None, - /, - ) -> None: ... +# if sys.version_info >= (3, 13): +# def exec( +# source: str | ReadableBuffer | CodeType, +# /, +# globals: dict[str, Any] | None = None, +# locals: Mapping[str, object] | None = None, +# *, +# closure: tuple[CellType, ...] | None = None, +# ) -> None: ... + +# elif sys.version_info >= (3, 11): +# def exec( +# source: str | ReadableBuffer | CodeType, +# globals: dict[str, Any] | None = None, +# locals: Mapping[str, object] | None = None, +# /, +# *, +# closure: tuple[CellType, ...] | None = None, +# ) -> None: ... + +# else: +# def exec( +# source: str | ReadableBuffer | CodeType, +# globals: dict[str, Any] | None = None, +# locals: Mapping[str, object] | None = None, +# /, +# ) -> None: ... exit: _sitebuiltins.Quitter From 2042c687b10da2a501d3b42660577f74718324e7 Mon Sep 17 00:00:00 2001 From: David Peter Date: Tue, 17 Dec 2024 11:42:19 +0100 Subject: [PATCH 04/65] Fix string annotation tests --- crates/red_knot_python_semantic/src/types/infer.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index f7ee8577d2267..f458967cad810 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -3115,8 +3115,7 @@ impl<'db> TypeInferenceBuilder<'db> { self.deferred_state.in_string_annotation(), "Expected the symbol table to create a symbol for every Name node" ); - // (None, Some(Boundness::PossiblyUnbound)) - Symbol::Type(Type::Unknown, Boundness::PossiblyUnbound) + Symbol::Unbound } } else { let use_id = name.scoped_use_id(self.db(), self.scope()); From 76e277b02a428d489a8848054e7d5bd7c1b39297 Mon Sep 17 00:00:00 2001 From: David Peter Date: Tue, 17 Dec 2024 11:58:35 +0100 Subject: [PATCH 05/65] Reset symbol states after if-elif-else chains --- .../src/semantic_index/builder.rs | 7 +++ .../src/semantic_index/use_def.rs | 16 +++++ .../semantic_index/use_def/symbol_state.rs | 10 ++++ .../vendor/typeshed/stdlib/builtins.pyi | 58 +++++++++---------- 4 files changed, 62 insertions(+), 29 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index/builder.rs b/crates/red_knot_python_semantic/src/semantic_index/builder.rs index 12e85e656da82..b53c0155545b2 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder.rs @@ -304,6 +304,11 @@ impl<'db> SemanticIndexBuilder<'db> { .record_visibility_constraint(&VisibilityConstraintRef::Single(constraint)) } + fn reset_visibility_constraints(&mut self, snapshot: FlowSnapshot) { + self.current_use_def_map_mut() + .reset_visibility_constraints(snapshot); + } + fn record_negated_visibility_constraint(&mut self, constraint: ScopedVisibilityConstraintId) { self.current_use_def_map_mut() .record_visibility_constraint(&VisibilityConstraintRef::Negated(constraint)); @@ -875,6 +880,8 @@ where for post_clause_state in post_clauses { self.flow_merge(post_clause_state); } + + self.reset_visibility_constraints(pre_if); } ast::Stmt::While(ast::StmtWhile { test, diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index 4364bbeb5a031..56f1647007608 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -534,6 +534,22 @@ impl<'db> UseDefMapBuilder<'db> { new_constraint_id } + pub(super) fn reset_visibility_constraints(&mut self, snapshot: FlowSnapshot) { + let num_symbols = self.symbol_states.len(); + debug_assert!(num_symbols >= snapshot.symbol_states.len()); + + self.unbound_visibility_constraint_id = snapshot.unbound_visibility_constraint_id; + + let mut snapshot_definitions_iter = snapshot.symbol_states.into_iter(); + for current in &mut self.symbol_states { + if let Some(snapshot) = snapshot_definitions_iter.next() { + current.reset_visibility_constraints(snapshot); + } else { + // Symbol not present in snapshot, keep visibility constraints + } + } + } + pub(super) fn record_declaration( &mut self, symbol: ScopedSymbolId, diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs index e1315d532af43..7cd740585b5cb 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs @@ -289,6 +289,16 @@ impl SymbolState { .record_visibility_constraint(all_visibility_constraints, constraint); } + pub(super) fn reset_visibility_constraints(&mut self, snapshot_state: SymbolState) { + if self.bindings.live_bindings == snapshot_state.bindings.live_bindings { + self.bindings.visibility_constraints = snapshot_state.bindings.visibility_constraints; + } + if self.declarations.live_declarations == snapshot_state.declarations.live_declarations { + self.declarations.visibility_constraints = + snapshot_state.declarations.visibility_constraints; + } + } + /// Record a newly-encountered declaration of this symbol. pub(super) fn record_declaration(&mut self, declaration_id: ScopedDefinitionId) { self.declarations.record_declaration(declaration_id); diff --git a/crates/red_knot_vendored/vendor/typeshed/stdlib/builtins.pyi b/crates/red_knot_vendored/vendor/typeshed/stdlib/builtins.pyi index 037d6d0c64b20..ad10ba9dff4cf 100644 --- a/crates/red_knot_vendored/vendor/typeshed/stdlib/builtins.pyi +++ b/crates/red_knot_vendored/vendor/typeshed/stdlib/builtins.pyi @@ -1392,33 +1392,33 @@ else: ) -> Any: ... # Comment above regarding `eval` applies to `exec` as well -# if sys.version_info >= (3, 13): -# def exec( -# source: str | ReadableBuffer | CodeType, -# /, -# globals: dict[str, Any] | None = None, -# locals: Mapping[str, object] | None = None, -# *, -# closure: tuple[CellType, ...] | None = None, -# ) -> None: ... - -# elif sys.version_info >= (3, 11): -# def exec( -# source: str | ReadableBuffer | CodeType, -# globals: dict[str, Any] | None = None, -# locals: Mapping[str, object] | None = None, -# /, -# *, -# closure: tuple[CellType, ...] | None = None, -# ) -> None: ... - -# else: -# def exec( -# source: str | ReadableBuffer | CodeType, -# globals: dict[str, Any] | None = None, -# locals: Mapping[str, object] | None = None, -# /, -# ) -> None: ... +if sys.version_info >= (3, 13): + def exec( + source: str | ReadableBuffer | CodeType, + /, + globals: dict[str, Any] | None = None, + locals: Mapping[str, object] | None = None, + *, + closure: tuple[CellType, ...] | None = None, + ) -> None: ... + +elif sys.version_info >= (3, 11): + def exec( + source: str | ReadableBuffer | CodeType, + globals: dict[str, Any] | None = None, + locals: Mapping[str, object] | None = None, + /, + *, + closure: tuple[CellType, ...] | None = None, + ) -> None: ... + +else: + def exec( + source: str | ReadableBuffer | CodeType, + globals: dict[str, Any] | None = None, + locals: Mapping[str, object] | None = None, + /, + ) -> None: ... exit: _sitebuiltins.Quitter @@ -1931,8 +1931,8 @@ class OSError(Exception): EnvironmentError = OSError IOError = OSError -# if sys.platform == "win32": -# WindowsError = OSError +if sys.platform == "win32": + WindowsError = OSError class ArithmeticError(Exception): ... class AssertionError(Exception): ... From 3d85c7d09c7ffc19f3933fef97bbe6e455ec19df Mon Sep 17 00:00:00 2001 From: David Peter Date: Tue, 17 Dec 2024 13:25:01 +0100 Subject: [PATCH 06/65] Add support for sys.platform --- .../resources/mdtest/sys_platform.md | 43 +++++++++++++++++++ crates/red_knot_python_semantic/src/db.rs | 6 ++- crates/red_knot_python_semantic/src/lib.rs | 2 + .../src/module_resolver/resolver.rs | 6 ++- .../src/module_resolver/testing.rs | 12 +++++- .../red_knot_python_semantic/src/program.rs | 13 ++++-- .../src/python_platform.rs | 13 ++++++ crates/red_knot_python_semantic/src/types.rs | 15 +++++++ crates/red_knot_test/src/config.rs | 13 +++++- crates/red_knot_test/src/db.rs | 5 ++- crates/red_knot_test/src/lib.rs | 3 ++ .../src/workspace/settings.rs | 1 + crates/ruff_graph/src/db.rs | 1 + 13 files changed, 122 insertions(+), 11 deletions(-) create mode 100644 crates/red_knot_python_semantic/resources/mdtest/sys_platform.md create mode 100644 crates/red_knot_python_semantic/src/python_platform.rs diff --git a/crates/red_knot_python_semantic/resources/mdtest/sys_platform.md b/crates/red_knot_python_semantic/resources/mdtest/sys_platform.md new file mode 100644 index 0000000000000..40cf1e2b6710c --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/sys_platform.md @@ -0,0 +1,43 @@ +# `sys.platform` + +## Default value + +When no target platform is specified, we fall back to the type of `sys.platform` declared in +typeshed: + +```toml +[environment] +# No python-platform entry +``` + +```py +import sys + +reveal_type(sys.platform) # revealed: str +``` + +## Explicit selection of `all` platforms + +```toml +[environment] +python-platform = "all" +``` + +```py +import sys + +reveal_type(sys.platform) # revealed: str +``` + +## Explicit selection of a specific platform + +```toml +[environment] +python-platform = "linux" +``` + +```py +import sys + +reveal_type(sys.platform) # revealed: Literal["linux"] +``` diff --git a/crates/red_knot_python_semantic/src/db.rs b/crates/red_knot_python_semantic/src/db.rs index 1e40d7ad1377b..e3677f816c37d 100644 --- a/crates/red_knot_python_semantic/src/db.rs +++ b/crates/red_knot_python_semantic/src/db.rs @@ -16,7 +16,7 @@ pub(crate) mod tests { use crate::program::{Program, SearchPathSettings}; use crate::python_version::PythonVersion; - use crate::{default_lint_registry, ProgramSettings}; + use crate::{default_lint_registry, ProgramSettings, PythonPlatform}; use super::Db; use crate::lint::RuleSelection; @@ -127,6 +127,8 @@ pub(crate) mod tests { pub(crate) struct TestDbBuilder<'a> { /// Target Python version python_version: PythonVersion, + /// Target Python platform + python_platform: PythonPlatform, /// Path to a custom typeshed directory custom_typeshed: Option, /// Path and content pairs for files that should be present @@ -137,6 +139,7 @@ pub(crate) mod tests { pub(crate) fn new() -> Self { Self { python_version: PythonVersion::default(), + python_platform: PythonPlatform::default(), custom_typeshed: None, files: vec![], } @@ -173,6 +176,7 @@ pub(crate) mod tests { &db, &ProgramSettings { python_version: self.python_version, + python_platform: self.python_platform, search_paths, }, ) diff --git a/crates/red_knot_python_semantic/src/lib.rs b/crates/red_knot_python_semantic/src/lib.rs index 324d2756b8c55..b05a41a0ef4c2 100644 --- a/crates/red_knot_python_semantic/src/lib.rs +++ b/crates/red_knot_python_semantic/src/lib.rs @@ -7,6 +7,7 @@ pub use db::Db; pub use module_name::ModuleName; pub use module_resolver::{resolve_module, system_module_search_paths, KnownModule, Module}; pub use program::{Program, ProgramSettings, SearchPathSettings, SitePackages}; +pub use python_platform::PythonPlatform; pub use python_version::PythonVersion; pub use semantic_model::{HasTy, SemanticModel}; @@ -17,6 +18,7 @@ mod module_name; mod module_resolver; mod node_key; mod program; +mod python_platform; mod python_version; pub mod semantic_index; mod semantic_model; diff --git a/crates/red_knot_python_semantic/src/module_resolver/resolver.rs b/crates/red_knot_python_semantic/src/module_resolver/resolver.rs index 990fc01a51106..954ded42620bb 100644 --- a/crates/red_knot_python_semantic/src/module_resolver/resolver.rs +++ b/crates/red_knot_python_semantic/src/module_resolver/resolver.rs @@ -721,8 +721,8 @@ mod tests { use crate::module_name::ModuleName; use crate::module_resolver::module::ModuleKind; use crate::module_resolver::testing::{FileSpec, MockedTypeshed, TestCase, TestCaseBuilder}; - use crate::ProgramSettings; use crate::PythonVersion; + use crate::{ProgramSettings, PythonPlatform}; use super::*; @@ -1262,7 +1262,7 @@ mod tests { fn symlink() -> anyhow::Result<()> { use anyhow::Context; - use crate::program::Program; + use crate::{program::Program, PythonPlatform}; use ruff_db::system::{OsSystem, SystemPath}; use crate::db::tests::TestDb; @@ -1296,6 +1296,7 @@ mod tests { &db, &ProgramSettings { python_version: PythonVersion::PY38, + python_platform: PythonPlatform::default(), search_paths: SearchPathSettings { extra_paths: vec![], src_root: src.clone(), @@ -1801,6 +1802,7 @@ not_a_directory &db, &ProgramSettings { python_version: PythonVersion::default(), + python_platform: PythonPlatform::default(), search_paths: SearchPathSettings { extra_paths: vec![], src_root: SystemPathBuf::from("/src"), diff --git a/crates/red_knot_python_semantic/src/module_resolver/testing.rs b/crates/red_knot_python_semantic/src/module_resolver/testing.rs index 75d66835f5f1a..365dec2893690 100644 --- a/crates/red_knot_python_semantic/src/module_resolver/testing.rs +++ b/crates/red_knot_python_semantic/src/module_resolver/testing.rs @@ -4,7 +4,7 @@ use ruff_db::vendored::VendoredPathBuf; use crate::db::tests::TestDb; use crate::program::{Program, SearchPathSettings}; use crate::python_version::PythonVersion; -use crate::{ProgramSettings, SitePackages}; +use crate::{ProgramSettings, PythonPlatform, SitePackages}; /// A test case for the module resolver. /// @@ -101,6 +101,7 @@ pub(crate) struct UnspecifiedTypeshed; pub(crate) struct TestCaseBuilder { typeshed_option: T, python_version: PythonVersion, + python_platform: PythonPlatform, first_party_files: Vec, site_packages_files: Vec, } @@ -147,6 +148,7 @@ impl TestCaseBuilder { Self { typeshed_option: UnspecifiedTypeshed, python_version: PythonVersion::default(), + python_platform: PythonPlatform::default(), first_party_files: vec![], site_packages_files: vec![], } @@ -157,12 +159,14 @@ impl TestCaseBuilder { let TestCaseBuilder { typeshed_option: _, python_version, + python_platform, first_party_files, site_packages_files, } = self; TestCaseBuilder { typeshed_option: VendoredTypeshed, python_version, + python_platform, first_party_files, site_packages_files, } @@ -176,6 +180,7 @@ impl TestCaseBuilder { let TestCaseBuilder { typeshed_option: _, python_version, + python_platform, first_party_files, site_packages_files, } = self; @@ -183,6 +188,7 @@ impl TestCaseBuilder { TestCaseBuilder { typeshed_option: typeshed, python_version, + python_platform, first_party_files, site_packages_files, } @@ -212,6 +218,7 @@ impl TestCaseBuilder { let TestCaseBuilder { typeshed_option, python_version, + python_platform, first_party_files, site_packages_files, } = self; @@ -227,6 +234,7 @@ impl TestCaseBuilder { &db, &ProgramSettings { python_version, + python_platform, search_paths: SearchPathSettings { extra_paths: vec![], src_root: src.clone(), @@ -269,6 +277,7 @@ impl TestCaseBuilder { let TestCaseBuilder { typeshed_option: VendoredTypeshed, python_version, + python_platform, first_party_files, site_packages_files, } = self; @@ -283,6 +292,7 @@ impl TestCaseBuilder { &db, &ProgramSettings { python_version, + python_platform, search_paths: SearchPathSettings { site_packages: SitePackages::Known(vec![site_packages.clone()]), ..SearchPathSettings::new(src.clone()) diff --git a/crates/red_knot_python_semantic/src/program.rs b/crates/red_knot_python_semantic/src/program.rs index cf85fd7cf41eb..54147cfccfb63 100644 --- a/crates/red_knot_python_semantic/src/program.rs +++ b/crates/red_knot_python_semantic/src/program.rs @@ -1,3 +1,4 @@ +use crate::python_platform::PythonPlatform; use crate::python_version::PythonVersion; use anyhow::Context; use salsa::Durability; @@ -12,6 +13,8 @@ use crate::Db; pub struct Program { pub python_version: PythonVersion, + pub python_platform: PythonPlatform, + #[return_ref] pub(crate) search_paths: SearchPaths, } @@ -20,6 +23,7 @@ impl Program { pub fn from_settings(db: &dyn Db, settings: &ProgramSettings) -> anyhow::Result { let ProgramSettings { python_version, + python_platform, search_paths, } = settings; @@ -28,9 +32,11 @@ impl Program { let search_paths = SearchPaths::from_settings(db, search_paths) .with_context(|| "Invalid search path settings")?; - Ok(Program::builder(settings.python_version, search_paths) - .durability(Durability::HIGH) - .new(db)) + Ok( + Program::builder(*python_version, python_platform.clone(), search_paths) + .durability(Durability::HIGH) + .new(db), + ) } pub fn update_search_paths( @@ -57,6 +63,7 @@ impl Program { #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct ProgramSettings { pub python_version: PythonVersion, + pub python_platform: PythonPlatform, pub search_paths: SearchPathSettings, } diff --git a/crates/red_knot_python_semantic/src/python_platform.rs b/crates/red_knot_python_semantic/src/python_platform.rs new file mode 100644 index 0000000000000..8a1fed6f20ed3 --- /dev/null +++ b/crates/red_knot_python_semantic/src/python_platform.rs @@ -0,0 +1,13 @@ +use serde::{Deserialize, Serialize}; + +/// The target platform to assume when resolving types. +#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub enum PythonPlatform { + /// Do not make any assumptions about the target platform. + #[default] + All, + /// Assume a target platform like `linux`, `darwin`, `win32`, etc. + #[serde(untagged)] + Individual(String), +} diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index ee5d2c418c5a0..5055e4b21c16c 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -137,6 +137,21 @@ fn symbol<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str) -> Symbol<'db> { return Symbol::Type(Type::BooleanLiteral(true), Boundness::Bound); } + if name == "platform" + && file_to_module(db, scope.file(db)).is_some_and(|module| module.name() == "sys") + { + match Program::get(db).python_platform(db) { + crate::PythonPlatform::Individual(platform) => { + return Symbol::Type( + Type::StringLiteral(StringLiteralType::new(db, platform.as_str())), + Boundness::Bound, + ); + } + crate::PythonPlatform::All => { + // Fall through to the looked up type + } + } + } let table = symbol_table(db, scope); table diff --git a/crates/red_knot_test/src/config.rs b/crates/red_knot_test/src/config.rs index cf677c485c3da..a2fe51226e34a 100644 --- a/crates/red_knot_test/src/config.rs +++ b/crates/red_knot_test/src/config.rs @@ -9,7 +9,7 @@ //! ``` use anyhow::Context; -use red_knot_python_semantic::PythonVersion; +use red_knot_python_semantic::{PythonPlatform, PythonVersion}; use serde::Deserialize; #[derive(Deserialize, Debug, Default, Clone)] @@ -28,13 +28,22 @@ impl MarkdownTestConfig { pub(crate) fn python_version(&self) -> Option { self.environment.as_ref().and_then(|env| env.python_version) } + + pub(crate) fn python_platform(&self) -> Option { + self.environment + .as_ref() + .and_then(|env| env.python_platform.clone()) + } } #[derive(Deserialize, Debug, Default, Clone)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub(crate) struct Environment { - /// Python version to assume when resolving types. + /// Target Python version to assume when resolving types. pub(crate) python_version: Option, + + /// Target platform to assume when resolving types. + pub(crate) python_platform: Option, } #[derive(Deserialize, Debug, Clone)] diff --git a/crates/red_knot_test/src/db.rs b/crates/red_knot_test/src/db.rs index df1cfc503e9f9..d6e3d82090e03 100644 --- a/crates/red_knot_test/src/db.rs +++ b/crates/red_knot_test/src/db.rs @@ -1,7 +1,7 @@ use red_knot_python_semantic::lint::RuleSelection; use red_knot_python_semantic::{ - default_lint_registry, Db as SemanticDb, Program, ProgramSettings, PythonVersion, - SearchPathSettings, + default_lint_registry, Db as SemanticDb, Program, ProgramSettings, PythonPlatform, + PythonVersion, SearchPathSettings, }; use ruff_db::files::{File, Files}; use ruff_db::system::{DbWithTestSystem, System, SystemPath, SystemPathBuf, TestSystem}; @@ -40,6 +40,7 @@ impl Db { &db, &ProgramSettings { python_version: PythonVersion::default(), + python_platform: PythonPlatform::default(), search_paths: SearchPathSettings::new(db.workspace_root.clone()), }, ) diff --git a/crates/red_knot_test/src/lib.rs b/crates/red_knot_test/src/lib.rs index f2a7639e29ec7..9b39048a572af 100644 --- a/crates/red_knot_test/src/lib.rs +++ b/crates/red_knot_test/src/lib.rs @@ -52,6 +52,9 @@ pub fn run(path: &Utf8Path, long_title: &str, short_title: &str, test_name: &str Program::get(&db) .set_python_version(&mut db) .to(test.configuration().python_version().unwrap_or_default()); + Program::get(&db) + .set_python_platform(&mut db) + .to(test.configuration().python_platform().unwrap_or_default()); // Remove all files so that the db is in a "fresh" state. db.memory_file_system().remove_all(); diff --git a/crates/red_knot_workspace/src/workspace/settings.rs b/crates/red_knot_workspace/src/workspace/settings.rs index 66493f441d265..525357e184753 100644 --- a/crates/red_knot_workspace/src/workspace/settings.rs +++ b/crates/red_knot_workspace/src/workspace/settings.rs @@ -40,6 +40,7 @@ impl Configuration { WorkspaceSettings { program: ProgramSettings { python_version: self.python_version.unwrap_or_default(), + python_platform: Default::default(), search_paths: self.search_paths.to_settings(workspace_root), }, } diff --git a/crates/ruff_graph/src/db.rs b/crates/ruff_graph/src/db.rs index a70ae6d91e7b8..c21b27715e542 100644 --- a/crates/ruff_graph/src/db.rs +++ b/crates/ruff_graph/src/db.rs @@ -49,6 +49,7 @@ impl ModuleDb { &db, &ProgramSettings { python_version, + python_platform: Default::default(), search_paths, }, )?; From dc9fbaaef1b5162b416bcf77b4605fbd3fa6578d Mon Sep 17 00:00:00 2001 From: David Peter Date: Tue, 17 Dec 2024 13:47:22 +0100 Subject: [PATCH 07/65] Add tests for common use cases --- .../mdtest/statically_known_branches.md | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md b/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md index 806391e0678e4..268e9c05f75fa 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md +++ b/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md @@ -43,6 +43,125 @@ def f(s: SomeType) -> None: ... The rest of this document contains tests for various cases where this feature can be used. +## Common use cases + +This section makes sure that we can handle all commonly encountered patterns of static conditions. + +### `sys.version_info` + +```toml +[environment] +python-version = "3.10" +``` + +```py +import sys + +if sys.version_info >= (3, 11): + greater_equals_311 = True +elif sys.version_info >= (3, 9): + greater_equals_309 = True +else: + less_than_309 = True + +if sys.version_info[0] == 2: + python2 = True + +# error: [unresolved-reference] +greater_equals_311 + +# no error +greater_equals_309 + +# error: [unresolved-reference] +less_than_309 + +# error: [unresolved-reference] +python2 +``` + +### `sys.platform` + +```toml +[environment] +python-platform = "linux" +``` + +```py +import sys + +if sys.platform == "linux": + linux = True +elif sys.platform == "darwin": + darwin = True +else: + other = True + +# no error +linux + +# error: [unresolved-reference] +darwin + +# error: [unresolved-reference] +other +``` + +### `typing.TYPE_CHECKING` + +```py +import typing + +if typing.TYPE_CHECKING: + type_checking = True +else: + runtime = True + +# no error +type_checking + +# error: [unresolved-reference] +runtime +``` + +### Combination of `sys.platform` check and `sys.version_info` check + +```toml +[environment] +python-version = "3.10" +python-platform = "darwin" +``` + +```py +import sys + +if sys.platform == "darwin" and sys.version_info >= (3, 11): + only_platform_check_true = True +elif sys.platform == "win32" and sys.version_info >= (3, 10): + only_version_check_true = True +elif sys.platform == "linux" and sys.version_info >= (3, 11): + both_checks_false = True +elif sys.platform == "darwin" and sys.version_info >= (3, 10): + both_checks_true = True +else: + other = True + +# error: [unresolved-reference] +only_platform_check_true + +# error: [unresolved-reference] +only_version_check_true + +# error: [unresolved-reference] +both_checks_false + +# no error +both_checks_true + +# error: [unresolved-reference] +other +``` + ## If statements ### Always false From c28bcdbc20b48d77043f412ca166b8e9d6e65097 Mon Sep 17 00:00:00 2001 From: David Peter Date: Tue, 17 Dec 2024 14:09:58 +0100 Subject: [PATCH 08/65] Fix match control flow --- .../src/semantic_index/builder.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index/builder.rs b/crates/red_knot_python_semantic/src/semantic_index/builder.rs index b53c0155545b2..9372b599cf909 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder.rs @@ -1028,23 +1028,24 @@ where let vis_constraint_id = self.record_visibility_constraint(constraint_id); vis_constraints.push(vis_constraint_id); } - for post_clause_state in post_case_snapshots { - self.flow_merge(post_clause_state); - } + + // If there is no final wildcard match case, pretend there is one. This is similar to how + // we add an implicit `else` block in if-elif chains, in case it's not present. if !cases .last() .is_some_and(|case| case.guard.is_none() && case.pattern.is_wildcard()) { - self.flow_merge(after_subject); - - // for post_clause_state in post_case_snapshots { - // self.flow_merge(post_clause_state); - // } + post_case_snapshots.push(self.flow_snapshot()); + self.flow_restore(after_subject.clone()); for id in &vis_constraints { self.record_negated_visibility_constraint(*id); } } + + for post_clause_state in post_case_snapshots { + self.flow_merge(post_clause_state); + } } ast::Stmt::Try(ast::StmtTry { body, From 2b12c496fff7eb69ffaab9df319d3d1e76040d5a Mon Sep 17 00:00:00 2001 From: David Peter Date: Tue, 17 Dec 2024 14:36:36 +0100 Subject: [PATCH 09/65] Fix match control flow --- .../mdtest/statically_known_branches.md | 105 +++++++++++++++++- .../src/semantic_index/builder.rs | 23 +++- .../src/semantic_index/constraint.rs | 4 +- crates/red_knot_python_semantic/src/types.rs | 4 + .../src/types/infer.rs | 4 +- .../src/types/narrow.rs | 4 +- .../src/types/static_truthiness.rs | 14 ++- 7 files changed, 140 insertions(+), 18 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md b/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md index 268e9c05f75fa..8cefccc342063 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md +++ b/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md @@ -902,6 +902,42 @@ match "a": reveal_type(x) # revealed: Literal[2] ``` +### Single-valued types, always true, with wildcard pattern + +```py +x = 1 + +match "a": + case "a": + x = 2 + case "b": + x = 3 + case _: + pass + +reveal_type(x) # revealed: Literal[2] +``` + +### Single-valued types, always true, with guard + +Make sure we don't infer a static truthiness in case there is a case guard: + +```py +def flag() -> bool: ... + +x = 1 + +match "a": + case "a" if flag(): + x = 2 + case "b": + x = 3 + case _: + pass + +reveal_type(x) # revealed: Literal[1, 2] +``` + ### Single-valued types, always false ```py @@ -916,18 +952,40 @@ match "something else": reveal_type(x) # revealed: Literal[1] ``` -### Single-valued types, with wildcard pattern +### Single-valued types, always false, with wildcard pattern ```py x = 1 -match "a": +match "something else": case "a": x = 2 + case "b": + x = 3 case _: + pass + +reveal_type(x) # revealed: Literal[1] +``` + +### Single-valued types, always false, with guard + +For definitely-false cases, the presence of a guard has no influence: + +```py +def flag() -> bool: ... + +x = 1 + +match "something else": + case "a" if flag(): + x = 2 + case "b": x = 3 + case _: + pass -reveal_type(x) # revealed: Literal[2] +reveal_type(x) # revealed: Literal[1] ``` ### Non-single-valued types @@ -937,13 +995,48 @@ def _(s: str): match s: case "a": x = 1 - case _: + case "b": x = 2 + case _: + x = 3 - reveal_type(x) # revealed: Literal[1, 2] + reveal_type(x) # revealed: Literal[1, 2, 3] ``` -### `sys.version_info` +### Matching on `sys.platform` + +```toml +[environment] +python-platform = "darwin" +``` + +```py +import sys + +match sys.platform: + case "linux": + linux = True + case "darwin": + darwin = True + case "win32": + win32 = True + case _: + other = True + +# error: [unresolved-reference] +linux + +# no error +darwin + +# error: [unresolved-reference] +win32 + +# error: [unresolved-reference] +other +``` + +### Matching on `sys.version_info` ```toml [environment] diff --git a/crates/red_knot_python_semantic/src/semantic_index/builder.rs b/crates/red_knot_python_semantic/src/semantic_index/builder.rs index 9372b599cf909..4735038afa0f6 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder.rs @@ -352,13 +352,18 @@ impl<'db> SemanticIndexBuilder<'db> { &mut self, subject: Expression<'db>, pattern: &ast::Pattern, + guard: Option<&ast::Expr>, ) -> ScopedConstraintId { + let guard = guard.map(|guard| self.add_standalone_expression(guard)); + let kind = match pattern { Pattern::MatchValue(pattern) => { let value = self.add_standalone_expression(&pattern.value); - PatternConstraintKind::Value(value) + PatternConstraintKind::Value(value, guard) + } + Pattern::MatchSingleton(singleton) => { + PatternConstraintKind::Singleton(singleton.value, guard) } - Pattern::MatchSingleton(singleton) => PatternConstraintKind::Singleton(singleton.value), _ => PatternConstraintKind::Unsupported, }; @@ -1007,7 +1012,11 @@ where return; }; - let first_constraint_id = self.add_pattern_constraint(subject_expr, &first.pattern); + let first_constraint_id = self.add_pattern_constraint( + subject_expr, + &first.pattern, + first.guard.as_deref(), + ); self.visit_match_case(first); @@ -1019,7 +1028,11 @@ where for case in remaining { post_case_snapshots.push(self.flow_snapshot()); self.flow_restore(after_subject.clone()); - let constraint_id = self.add_pattern_constraint(subject_expr, &case.pattern); + let constraint_id = self.add_pattern_constraint( + subject_expr, + &case.pattern, + case.guard.as_deref(), + ); self.visit_match_case(case); for id in &vis_constraints { @@ -1046,6 +1059,8 @@ where for post_clause_state in post_case_snapshots { self.flow_merge(post_clause_state); } + + self.reset_visibility_constraints(after_subject); } ast::Stmt::Try(ast::StmtTry { body, diff --git a/crates/red_knot_python_semantic/src/semantic_index/constraint.rs b/crates/red_knot_python_semantic/src/semantic_index/constraint.rs index 347f0ebaac4f7..05aa225d063d5 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/constraint.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/constraint.rs @@ -20,8 +20,8 @@ pub(crate) enum ConstraintNode<'db> { /// Pattern kinds for which we do support type narrowing and/or static truthiness analysis. #[derive(Debug, Clone, PartialEq)] pub(crate) enum PatternConstraintKind<'db> { - Singleton(Singleton), - Value(Expression<'db>), + Singleton(Singleton, Option>), + Value(Expression<'db>, Option>), Unsupported, } diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 5055e4b21c16c..c62c7c6015394 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2949,6 +2949,10 @@ impl Truthiness { matches!(self, Truthiness::AlwaysFalse) } + pub(crate) const fn is_always_true(self) -> bool { + matches!(self, Truthiness::AlwaysTrue) + } + pub(crate) const fn negate(self) -> Self { match self { Self::AlwaysTrue => Self::AlwaysFalse, diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index f458967cad810..851d39fadd73f 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -1741,7 +1741,9 @@ impl<'db> TypeInferenceBuilder<'db> { guard, } = case; self.infer_match_pattern(pattern); - self.infer_optional_expression(guard.as_deref()); + guard + .as_deref() + .map(|guard| self.infer_standalone_expression(guard)); self.infer_body(body); } } diff --git a/crates/red_knot_python_semantic/src/types/narrow.rs b/crates/red_knot_python_semantic/src/types/narrow.rs index 5d0c2f5136bce..ef1d6293037ba 100644 --- a/crates/red_knot_python_semantic/src/types/narrow.rs +++ b/crates/red_knot_python_semantic/src/types/narrow.rs @@ -219,11 +219,11 @@ impl<'db> NarrowingConstraintsBuilder<'db> { let subject = pattern.subject(self.db); match pattern.kind(self.db) { - PatternConstraintKind::Singleton(singleton) => { + PatternConstraintKind::Singleton(singleton, _guard) => { self.evaluate_match_pattern_singleton(*subject, *singleton) } // TODO: support more pattern kinds - PatternConstraintKind::Value(_) | PatternConstraintKind::Unsupported => None, + PatternConstraintKind::Value(..) | PatternConstraintKind::Unsupported => None, } } diff --git a/crates/red_knot_python_semantic/src/types/static_truthiness.rs b/crates/red_knot_python_semantic/src/types/static_truthiness.rs index b4146af20d410..a8c0dee8f5988 100644 --- a/crates/red_knot_python_semantic/src/types/static_truthiness.rs +++ b/crates/red_knot_python_semantic/src/types/static_truthiness.rs @@ -31,7 +31,7 @@ pub(crate) fn static_visibility<'db>( ty.bool(db).negate_if(!constraint.is_positive) } ConstraintNode::Pattern(inner) => match inner.kind(db) { - PatternConstraintKind::Value(value) => { + PatternConstraintKind::Value(value, guard) => { let subject_expression = inner.subject(db); let inference = infer_expression_types(db, *subject_expression); let scope = subject_expression.scope(db); @@ -47,12 +47,20 @@ pub(crate) fn static_visibility<'db>( .expression_ty(value.node_ref(db).scoped_expression_id(db, scope)); if subject_ty.is_single_valued(db) { - Truthiness::from(subject_ty.is_equivalent_to(db, value_ty)) + let truthiness = + Truthiness::from(subject_ty.is_equivalent_to(db, value_ty)); + + if truthiness.is_always_true() && guard.is_some() { + // Fall back to ambiguous, the guard might change the result. + Truthiness::Ambiguous + } else { + truthiness + } } else { Truthiness::Ambiguous } } - PatternConstraintKind::Singleton(_) | PatternConstraintKind::Unsupported => { + PatternConstraintKind::Singleton(..) | PatternConstraintKind::Unsupported => { Truthiness::Ambiguous } }, From e871ea19e753a3aad20acf31cf378147aef84013 Mon Sep 17 00:00:00 2001 From: David Peter Date: Tue, 17 Dec 2024 17:20:25 +0100 Subject: [PATCH 10/65] Fix boolean expression tests --- .../src/semantic_index/builder.rs | 45 ++++++++++++++----- .../src/semantic_index/use_def.rs | 11 ++++- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index/builder.rs b/crates/red_knot_python_semantic/src/semantic_index/builder.rs index 4735038afa0f6..36b9cb9013515 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder.rs @@ -296,12 +296,20 @@ impl<'db> SemanticIndexBuilder<'db> { self.current_use_def_map_mut().record_constraint(constraint) } + fn add_visibility_constraint( + &mut self, + constraint: ScopedConstraintId, + ) -> ScopedVisibilityConstraintId { + self.current_use_def_map_mut() + .add_visibility_constraint(VisibilityConstraintRef::Single(constraint)) + } + fn record_visibility_constraint( &mut self, constraint: ScopedConstraintId, ) -> ScopedVisibilityConstraintId { self.current_use_def_map_mut() - .record_visibility_constraint(&VisibilityConstraintRef::Single(constraint)) + .record_visibility_constraint(VisibilityConstraintRef::Single(constraint)) } fn reset_visibility_constraints(&mut self, snapshot: FlowSnapshot) { @@ -309,9 +317,12 @@ impl<'db> SemanticIndexBuilder<'db> { .reset_visibility_constraints(snapshot); } - fn record_negated_visibility_constraint(&mut self, constraint: ScopedVisibilityConstraintId) { + fn record_negated_visibility_constraint( + &mut self, + constraint: ScopedVisibilityConstraintId, + ) -> ScopedVisibilityConstraintId { self.current_use_def_map_mut() - .record_visibility_constraint(&VisibilityConstraintRef::Negated(constraint)); + .record_visibility_constraint(VisibilityConstraintRef::Negated(constraint)) } fn build_constraint(&mut self, constraint_node: &Expr) -> Constraint<'db> { @@ -1377,23 +1388,35 @@ where op, }) => { let mut snapshots = vec![]; - let mut last_constraint_id = None; + let mut constraints = vec![]; + for (index, value) in values.iter().enumerate() { self.visit_expr(value); - if let Some(last_constraint_id) = last_constraint_id { - self.record_visibility_constraint(last_constraint_id); + if let Some(last_constraint_id) = constraints.last() { + self.record_visibility_constraint(*last_constraint_id); } - // In the last value we don't need to take a snapshot nor add a constraint + // Snapshot is taken after visiting the expression but before adding the constraint. + snapshots.push(self.flow_snapshot()); if index < values.len() - 1 { - // Snapshot is taken after visiting the expression but before adding the constraint. - snapshots.push(self.flow_snapshot()); + // In the last value we don't need to add a constraint let constraint = self.build_constraint(value); - last_constraint_id = Some(match op { + let id = match op { BoolOp::And => self.record_constraint(constraint), BoolOp::Or => self.record_negated_constraint(constraint), - }); + }; + constraints.push(id); } } + + let mut snapshots = snapshots.into_iter(); + let first = snapshots.next().expect("at least one value"); + self.flow_restore(first); + + for id in constraints { + let vid = self.add_visibility_constraint(id); + self.record_negated_visibility_constraint(vid); + } + for snapshot in snapshots { self.flow_merge(snapshot); } diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index 56f1647007608..653fae6807aea 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -508,11 +508,18 @@ impl<'db> UseDefMapBuilder<'db> { constraint_id } + pub(super) fn add_visibility_constraint( + &mut self, + constraint: VisibilityConstraintRef, + ) -> ScopedVisibilityConstraintId { + self.all_visibility_constraints.push(constraint) + } + pub(super) fn record_visibility_constraint( &mut self, - constraint: &VisibilityConstraintRef, + constraint: VisibilityConstraintRef, ) -> ScopedVisibilityConstraintId { - let new_constraint_id = self.all_visibility_constraints.push(constraint.clone()); + let new_constraint_id = self.add_visibility_constraint(constraint); for state in &mut self.symbol_states { state.record_visibility_constraint( &mut self.all_visibility_constraints, From 00e698b346a2caef5670e558224573db903ff28f Mon Sep 17 00:00:00 2001 From: David Peter Date: Tue, 17 Dec 2024 17:21:24 +0100 Subject: [PATCH 11/65] Clippy suggestions --- crates/red_knot_workspace/src/workspace/settings.rs | 6 ++++-- crates/ruff_graph/src/db.rs | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/red_knot_workspace/src/workspace/settings.rs b/crates/red_knot_workspace/src/workspace/settings.rs index 525357e184753..c924e9ec9b1d4 100644 --- a/crates/red_knot_workspace/src/workspace/settings.rs +++ b/crates/red_knot_workspace/src/workspace/settings.rs @@ -1,5 +1,7 @@ use crate::workspace::PackageMetadata; -use red_knot_python_semantic::{ProgramSettings, PythonVersion, SearchPathSettings, SitePackages}; +use red_knot_python_semantic::{ + ProgramSettings, PythonPlatform, PythonVersion, SearchPathSettings, SitePackages, +}; use ruff_db::system::{SystemPath, SystemPathBuf}; /// The resolved configurations. @@ -40,7 +42,7 @@ impl Configuration { WorkspaceSettings { program: ProgramSettings { python_version: self.python_version.unwrap_or_default(), - python_platform: Default::default(), + python_platform: PythonPlatform::default(), search_paths: self.search_paths.to_settings(workspace_root), }, } diff --git a/crates/ruff_graph/src/db.rs b/crates/ruff_graph/src/db.rs index c21b27715e542..9e3d1b6f3f58d 100644 --- a/crates/ruff_graph/src/db.rs +++ b/crates/ruff_graph/src/db.rs @@ -3,7 +3,9 @@ use std::sync::Arc; use zip::CompressionMethod; use red_knot_python_semantic::lint::RuleSelection; -use red_knot_python_semantic::{Db, Program, ProgramSettings, PythonVersion, SearchPathSettings}; +use red_knot_python_semantic::{ + Db, Program, ProgramSettings, PythonPlatform, PythonVersion, SearchPathSettings, +}; use ruff_db::files::{File, Files}; use ruff_db::system::{OsSystem, System, SystemPathBuf}; use ruff_db::vendored::{VendoredFileSystem, VendoredFileSystemBuilder}; @@ -49,7 +51,7 @@ impl ModuleDb { &db, &ProgramSettings { python_version, - python_platform: Default::default(), + python_platform: PythonPlatform::default(), search_paths, }, )?; From 27b64a0c66b8b0e96452571736c03c4418c14cb6 Mon Sep 17 00:00:00 2001 From: David Peter Date: Tue, 17 Dec 2024 17:33:57 +0100 Subject: [PATCH 12/65] Update snapshots --- ...orkspace__metadata__tests__member_pattern_matching_file.snap | 2 +- ..._metadata__tests__member_pattern_matching_hidden_folder.snap | 2 +- ...__workspace__metadata__tests__package_without_pyproject.snap | 2 +- ...t_workspace__workspace__metadata__tests__single_package.snap | 2 +- ...rkspace__workspace__metadata__tests__workspace_excluded.snap | 2 +- ...orkspace__workspace__metadata__tests__workspace_members.snap | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__member_pattern_matching_file.snap b/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__member_pattern_matching_file.snap index d4a37a5d96369..804be9349a23c 100644 --- a/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__member_pattern_matching_file.snap +++ b/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__member_pattern_matching_file.snap @@ -1,7 +1,6 @@ --- source: crates/red_knot_workspace/src/workspace/metadata.rs expression: "&workspace" -snapshot_kind: text --- WorkspaceMetadata( root: "/app", @@ -23,6 +22,7 @@ WorkspaceMetadata( settings: WorkspaceSettings( program: ProgramSettings( python_version: "3.9", + python_platform: all, search_paths: SearchPathSettings( extra_paths: [], src_root: "/app", diff --git a/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__member_pattern_matching_hidden_folder.snap b/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__member_pattern_matching_hidden_folder.snap index ab387f9a4c834..3aabf1ceb2e35 100644 --- a/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__member_pattern_matching_hidden_folder.snap +++ b/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__member_pattern_matching_hidden_folder.snap @@ -1,7 +1,6 @@ --- source: crates/red_knot_workspace/src/workspace/metadata.rs expression: workspace -snapshot_kind: text --- WorkspaceMetadata( root: "/app", @@ -23,6 +22,7 @@ WorkspaceMetadata( settings: WorkspaceSettings( program: ProgramSettings( python_version: "3.9", + python_platform: all, search_paths: SearchPathSettings( extra_paths: [], src_root: "/app", diff --git a/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__package_without_pyproject.snap b/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__package_without_pyproject.snap index bbc5c1247c548..73329a8552048 100644 --- a/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__package_without_pyproject.snap +++ b/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__package_without_pyproject.snap @@ -1,7 +1,6 @@ --- source: crates/red_knot_workspace/src/workspace/metadata.rs expression: workspace -snapshot_kind: text --- WorkspaceMetadata( root: "/app", @@ -23,6 +22,7 @@ WorkspaceMetadata( settings: WorkspaceSettings( program: ProgramSettings( python_version: "3.9", + python_platform: all, search_paths: SearchPathSettings( extra_paths: [], src_root: "/app", diff --git a/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__single_package.snap b/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__single_package.snap index 4c0f26977bc24..6255e408683d9 100644 --- a/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__single_package.snap +++ b/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__single_package.snap @@ -1,7 +1,6 @@ --- source: crates/red_knot_workspace/src/workspace/metadata.rs expression: workspace -snapshot_kind: text --- WorkspaceMetadata( root: "/app", @@ -23,6 +22,7 @@ WorkspaceMetadata( settings: WorkspaceSettings( program: ProgramSettings( python_version: "3.9", + python_platform: all, search_paths: SearchPathSettings( extra_paths: [], src_root: "/app", diff --git a/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__workspace_excluded.snap b/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__workspace_excluded.snap index 8429a787eb208..4c68fd87142b2 100644 --- a/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__workspace_excluded.snap +++ b/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__workspace_excluded.snap @@ -1,7 +1,6 @@ --- source: crates/red_knot_workspace/src/workspace/metadata.rs expression: workspace -snapshot_kind: text --- WorkspaceMetadata( root: "/app", @@ -36,6 +35,7 @@ WorkspaceMetadata( settings: WorkspaceSettings( program: ProgramSettings( python_version: "3.9", + python_platform: all, search_paths: SearchPathSettings( extra_paths: [], src_root: "/app", diff --git a/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__workspace_members.snap b/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__workspace_members.snap index 74e5ced627f17..4f3c74ba34c8f 100644 --- a/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__workspace_members.snap +++ b/crates/red_knot_workspace/src/workspace/snapshots/red_knot_workspace__workspace__metadata__tests__workspace_members.snap @@ -1,7 +1,6 @@ --- source: crates/red_knot_workspace/src/workspace/metadata.rs expression: workspace -snapshot_kind: text --- WorkspaceMetadata( root: "/app", @@ -49,6 +48,7 @@ WorkspaceMetadata( settings: WorkspaceSettings( program: ProgramSettings( python_version: "3.9", + python_platform: all, search_paths: SearchPathSettings( extra_paths: [], src_root: "/app", From a37dac1b41f90b3ee2cf7ee216c1dc35e47ff607 Mon Sep 17 00:00:00 2001 From: David Peter Date: Tue, 17 Dec 2024 17:54:46 +0100 Subject: [PATCH 13/65] Fix serde feature compilation problem --- .../red_knot_python_semantic/src/python_platform.rs | 12 +++++++----- .../src/semantic_index/builder.rs | 8 +++++++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/crates/red_knot_python_semantic/src/python_platform.rs b/crates/red_knot_python_semantic/src/python_platform.rs index 8a1fed6f20ed3..d6affbfcebc38 100644 --- a/crates/red_knot_python_semantic/src/python_platform.rs +++ b/crates/red_knot_python_semantic/src/python_platform.rs @@ -1,13 +1,15 @@ -use serde::{Deserialize, Serialize}; - /// The target platform to assume when resolving types. -#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] +#[derive(Debug, Clone, Default, PartialEq, Eq)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "kebab-case") +)] pub enum PythonPlatform { /// Do not make any assumptions about the target platform. #[default] All, /// Assume a target platform like `linux`, `darwin`, `win32`, etc. - #[serde(untagged)] + #[cfg_attr(feature = "serde", serde(untagged))] Individual(String), } diff --git a/crates/red_knot_python_semantic/src/semantic_index/builder.rs b/crates/red_knot_python_semantic/src/semantic_index/builder.rs index 36b9cb9013515..bd62633d8a349 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder.rs @@ -929,7 +929,7 @@ where // We may execute the `else` clause without ever executing the body, so merge in // the pre-loop state before visiting `else`. - self.flow_merge(pre_loop); + self.flow_merge(pre_loop.clone()); self.record_negated_constraint(constraint); self.visit_body(orelse); @@ -940,6 +940,8 @@ where for break_state in break_states { self.flow_merge(break_state); } + + self.reset_visibility_constraints(pre_loop); } ast::Stmt::With(ast::StmtWith { items, @@ -1387,6 +1389,8 @@ where range: _, op, }) => { + let pre_op = self.flow_snapshot(); + let mut snapshots = vec![]; let mut constraints = vec![]; @@ -1420,6 +1424,8 @@ where for snapshot in snapshots { self.flow_merge(snapshot); } + + self.reset_visibility_constraints(pre_op); } _ => { walk_expr(self, expr); From e044fde784aa55b53af99ccbff121c3cacbd79a2 Mon Sep 17 00:00:00 2001 From: David Peter Date: Tue, 17 Dec 2024 20:20:21 +0100 Subject: [PATCH 14/65] Add recursion limit hack --- .../src/types/static_truthiness.rs | 57 +++++++++++++++++-- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/static_truthiness.rs b/crates/red_knot_python_semantic/src/types/static_truthiness.rs index a8c0dee8f5988..015c5930da745 100644 --- a/crates/red_knot_python_semantic/src/types/static_truthiness.rs +++ b/crates/red_knot_python_semantic/src/types/static_truthiness.rs @@ -9,6 +9,8 @@ use crate::semantic_index::{ use crate::types::{infer_expression_types, Truthiness}; use crate::Db; +const MAX_RECURSION_DEPTH: usize = 8; + /// Analyze the statically known visibility for a given visibility constraint. pub(crate) fn static_visibility<'db>( db: &'db dyn Db, @@ -16,6 +18,26 @@ pub(crate) fn static_visibility<'db>( all_visibility_constraints: &IndexVec, visibility_constraint_id: ScopedVisibilityConstraintId, ) -> Truthiness { + static_visibility_impl( + db, + all_constraints, + all_visibility_constraints, + visibility_constraint_id, + MAX_RECURSION_DEPTH, + ) +} + +fn static_visibility_impl<'db>( + db: &'db dyn Db, + all_constraints: &IndexVec>, + all_visibility_constraints: &IndexVec, + visibility_constraint_id: ScopedVisibilityConstraintId, + max_depth: usize, +) -> Truthiness { + if max_depth == 0 { + return Truthiness::Ambiguous; + } + let visibility_constraint = &all_visibility_constraints[visibility_constraint_id]; match visibility_constraint { VisibilityConstraintRef::Single(id) => { @@ -66,17 +88,30 @@ pub(crate) fn static_visibility<'db>( }, } } - VisibilityConstraintRef::Negated(visibility_constraint_id) => static_visibility( + VisibilityConstraintRef::Negated(visibility_constraint_id) => static_visibility_impl( db, all_constraints, all_visibility_constraints, *visibility_constraint_id, + max_depth - 1, ) .negate(), VisibilityConstraintRef::None => Truthiness::AlwaysTrue, VisibilityConstraintRef::And(lhs_id, rhs_id) => { - let lhs = static_visibility(db, all_constraints, all_visibility_constraints, *lhs_id); - let rhs = static_visibility(db, all_constraints, all_visibility_constraints, *rhs_id); + let lhs = static_visibility_impl( + db, + all_constraints, + all_visibility_constraints, + *lhs_id, + max_depth - 1, + ); + let rhs = static_visibility_impl( + db, + all_constraints, + all_visibility_constraints, + *rhs_id, + max_depth - 1, + ); if lhs == Truthiness::AlwaysFalse || rhs == Truthiness::AlwaysFalse { Truthiness::AlwaysFalse @@ -87,8 +122,20 @@ pub(crate) fn static_visibility<'db>( } } VisibilityConstraintRef::Or(lhs_id, rhs_id) => { - let lhs = static_visibility(db, all_constraints, all_visibility_constraints, *lhs_id); - let rhs = static_visibility(db, all_constraints, all_visibility_constraints, *rhs_id); + let lhs = static_visibility_impl( + db, + all_constraints, + all_visibility_constraints, + *lhs_id, + max_depth - 1, + ); + let rhs = static_visibility_impl( + db, + all_constraints, + all_visibility_constraints, + *rhs_id, + max_depth - 1, + ); if lhs == Truthiness::AlwaysFalse && rhs == Truthiness::AlwaysFalse { Truthiness::AlwaysFalse From 90e639bd2087df64105ae74e0b625ecfc58183a5 Mon Sep 17 00:00:00 2001 From: David Peter Date: Tue, 17 Dec 2024 20:36:46 +0100 Subject: [PATCH 15/65] Short circuit, increase threshold --- .../src/semantic_index/use_def/symbol_state.rs | 4 ++-- .../src/types/static_truthiness.rs | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs index 7cd740585b5cb..87b6a80167945 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs @@ -105,7 +105,7 @@ pub(super) struct SymbolDeclarations { /// [`BitSet`]: which declarations (as [`ScopedDefinitionId`]) can reach the current location? pub(crate) live_declarations: Declarations, - /// For each live declaration, which [`VisibilityConstraints`] were active at that declaration? + /// For each live declaration, which visibility constraints apply to it? pub(crate) visibility_constraints: VisibilityConstraintPerBinding, } @@ -176,7 +176,7 @@ pub(super) struct SymbolBindings { /// binding in `live_bindings`. constraints: ConstraintsPerBinding, - /// For each live binding, which [`VisibilityConstraints`] were active at that binding? + /// For each live binding, which visibility constraints apply to it? visibility_constraints: VisibilityConstraintPerBinding, } diff --git a/crates/red_knot_python_semantic/src/types/static_truthiness.rs b/crates/red_knot_python_semantic/src/types/static_truthiness.rs index 015c5930da745..aa3d5fb61086b 100644 --- a/crates/red_knot_python_semantic/src/types/static_truthiness.rs +++ b/crates/red_knot_python_semantic/src/types/static_truthiness.rs @@ -9,7 +9,7 @@ use crate::semantic_index::{ use crate::types::{infer_expression_types, Truthiness}; use crate::Db; -const MAX_RECURSION_DEPTH: usize = 8; +const MAX_RECURSION_DEPTH: usize = 10; /// Analyze the statically known visibility for a given visibility constraint. pub(crate) fn static_visibility<'db>( @@ -105,6 +105,11 @@ fn static_visibility_impl<'db>( *lhs_id, max_depth - 1, ); + + if lhs == Truthiness::AlwaysFalse { + return Truthiness::AlwaysFalse; + } + let rhs = static_visibility_impl( db, all_constraints, @@ -113,7 +118,7 @@ fn static_visibility_impl<'db>( max_depth - 1, ); - if lhs == Truthiness::AlwaysFalse || rhs == Truthiness::AlwaysFalse { + if rhs == Truthiness::AlwaysFalse { Truthiness::AlwaysFalse } else if lhs == Truthiness::AlwaysTrue && rhs == Truthiness::AlwaysTrue { Truthiness::AlwaysTrue @@ -129,6 +134,11 @@ fn static_visibility_impl<'db>( *lhs_id, max_depth - 1, ); + + if lhs == Truthiness::AlwaysTrue { + return Truthiness::AlwaysTrue; + } + let rhs = static_visibility_impl( db, all_constraints, @@ -139,7 +149,7 @@ fn static_visibility_impl<'db>( if lhs == Truthiness::AlwaysFalse && rhs == Truthiness::AlwaysFalse { Truthiness::AlwaysFalse - } else if lhs == Truthiness::AlwaysTrue || rhs == Truthiness::AlwaysTrue { + } else if rhs == Truthiness::AlwaysTrue { Truthiness::AlwaysTrue } else { Truthiness::Ambiguous From 3a1dbc182f2532aba336961650d916fa954a4017 Mon Sep 17 00:00:00 2001 From: David Peter Date: Tue, 17 Dec 2024 22:22:58 +0100 Subject: [PATCH 16/65] Renamings --- .../src/semantic_index/use_def.rs | 4 +-- .../semantic_index/use_def/symbol_state.rs | 14 +++++----- .../semantic_index/visibility_constraint.rs | 4 +-- crates/red_knot_python_semantic/src/types.rs | 8 +++--- ...tic_truthiness.rs => static_visibility.rs} | 26 +++++++++---------- 5 files changed, 29 insertions(+), 27 deletions(-) rename crates/red_knot_python_semantic/src/types/{static_truthiness.rs => static_visibility.rs} (90%) diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index 653fae6807aea..047f75d5b2486 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -532,7 +532,7 @@ impl<'db> UseDefMapBuilder<'db> { } else { self.unbound_visibility_constraint_id = self.all_visibility_constraints - .push(VisibilityConstraintRef::And( + .push(VisibilityConstraintRef::Sequence( self.unbound_visibility_constraint_id, new_constraint_id, )); @@ -662,7 +662,7 @@ impl<'db> UseDefMapBuilder<'db> { _ => { let constraint_id = self.all_visibility_constraints - .push(VisibilityConstraintRef::Or( + .push(VisibilityConstraintRef::Merged( self.unbound_visibility_constraint_id, snapshot.unbound_visibility_constraint_id, )); diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs index 87b6a80167945..4f9f9ccb3881e 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs @@ -142,7 +142,7 @@ impl SymbolDeclarations { *existing = constraint; } else { *existing = all_visibility_constraints - .push(VisibilityConstraintRef::And(*existing, constraint)); + .push(VisibilityConstraintRef::Sequence(*existing, constraint)); } } } @@ -225,7 +225,7 @@ impl SymbolBindings { *existing = constraint; } else { *existing = all_visibility_constraints - .push(VisibilityConstraintRef::And(*existing, constraint)); + .push(VisibilityConstraintRef::Sequence(*existing, constraint)); } } } @@ -436,8 +436,9 @@ impl SymbolState { *current = ScopedVisibilityConstraintId::from_u32(0); } _ => { - let constraint_id = all_visibility_constraints - .push(VisibilityConstraintRef::Or(*current, a_vis_constraint)); + let constraint_id = all_visibility_constraints.push( + VisibilityConstraintRef::Merged(*current, a_vis_constraint), + ); *current = constraint_id; } } @@ -520,8 +521,9 @@ impl SymbolState { *current = ScopedVisibilityConstraintId::from_u32(0); } _ => { - let constraint_id = all_visibility_constraints - .push(VisibilityConstraintRef::Or(*current, a_vis_constraint)); + let constraint_id = all_visibility_constraints.push( + VisibilityConstraintRef::Merged(*current, a_vis_constraint), + ); *current = constraint_id; } } diff --git a/crates/red_knot_python_semantic/src/semantic_index/visibility_constraint.rs b/crates/red_knot_python_semantic/src/semantic_index/visibility_constraint.rs index f2769cff77e42..761db197334a6 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/visibility_constraint.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/visibility_constraint.rs @@ -29,6 +29,6 @@ pub(crate) enum VisibilityConstraintRef { None, Single(ScopedConstraintId), Negated(ScopedVisibilityConstraintId), - And(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), - Or(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), + Sequence(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), + Merged(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), } diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index c62c7c6015394..207fb644f1340 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -33,7 +33,7 @@ use crate::types::diagnostic::INVALID_TYPE_FORM; use crate::types::mro::{Mro, MroError, MroIterator}; use crate::types::narrow::narrowing_constraint; use crate::{Db, FxOrderSet, Module, Program, PythonVersion}; -pub(crate) use static_truthiness::static_visibility; +pub(crate) use static_visibility::analyze; mod builder; mod call; @@ -45,7 +45,7 @@ mod infer; mod mro; mod narrow; mod signatures; -mod static_truthiness; +mod static_visibility; mod string_annotation; mod unpacker; @@ -279,7 +279,7 @@ fn bindings_ty<'db>( visibility_constraint, } in bindings_with_constraints { - let static_visibility = static_visibility( + let static_visibility = analyze( db, all_constraints, all_visibility_constraints, @@ -366,7 +366,7 @@ fn declarations_ty<'db>( for (declaration, all_constraints, all_visibility_constraints, visibility_constraint) in declarations { - let static_visibility = static_visibility( + let static_visibility = analyze( db, all_constraints, all_visibility_constraints, diff --git a/crates/red_knot_python_semantic/src/types/static_truthiness.rs b/crates/red_knot_python_semantic/src/types/static_visibility.rs similarity index 90% rename from crates/red_knot_python_semantic/src/types/static_truthiness.rs rename to crates/red_knot_python_semantic/src/types/static_visibility.rs index aa3d5fb61086b..456a574fd016c 100644 --- a/crates/red_knot_python_semantic/src/types/static_truthiness.rs +++ b/crates/red_knot_python_semantic/src/types/static_visibility.rs @@ -12,13 +12,13 @@ use crate::Db; const MAX_RECURSION_DEPTH: usize = 10; /// Analyze the statically known visibility for a given visibility constraint. -pub(crate) fn static_visibility<'db>( +pub(crate) fn analyze<'db>( db: &'db dyn Db, all_constraints: &IndexVec>, all_visibility_constraints: &IndexVec, visibility_constraint_id: ScopedVisibilityConstraintId, ) -> Truthiness { - static_visibility_impl( + analyze_impl( db, all_constraints, all_visibility_constraints, @@ -27,7 +27,7 @@ pub(crate) fn static_visibility<'db>( ) } -fn static_visibility_impl<'db>( +fn analyze_impl<'db>( db: &'db dyn Db, all_constraints: &IndexVec>, all_visibility_constraints: &IndexVec, @@ -88,7 +88,7 @@ fn static_visibility_impl<'db>( }, } } - VisibilityConstraintRef::Negated(visibility_constraint_id) => static_visibility_impl( + VisibilityConstraintRef::Negated(visibility_constraint_id) => analyze_impl( db, all_constraints, all_visibility_constraints, @@ -97,8 +97,8 @@ fn static_visibility_impl<'db>( ) .negate(), VisibilityConstraintRef::None => Truthiness::AlwaysTrue, - VisibilityConstraintRef::And(lhs_id, rhs_id) => { - let lhs = static_visibility_impl( + VisibilityConstraintRef::Sequence(lhs_id, rhs_id) => { + let lhs = analyze_impl( db, all_constraints, all_visibility_constraints, @@ -110,7 +110,7 @@ fn static_visibility_impl<'db>( return Truthiness::AlwaysFalse; } - let rhs = static_visibility_impl( + let rhs = analyze_impl( db, all_constraints, all_visibility_constraints, @@ -126,8 +126,8 @@ fn static_visibility_impl<'db>( Truthiness::Ambiguous } } - VisibilityConstraintRef::Or(lhs_id, rhs_id) => { - let lhs = static_visibility_impl( + VisibilityConstraintRef::Merged(lhs_id, rhs_id) => { + let lhs = analyze_impl( db, all_constraints, all_visibility_constraints, @@ -139,7 +139,7 @@ fn static_visibility_impl<'db>( return Truthiness::AlwaysTrue; } - let rhs = static_visibility_impl( + let rhs = analyze_impl( db, all_constraints, all_visibility_constraints, @@ -147,10 +147,10 @@ fn static_visibility_impl<'db>( max_depth - 1, ); - if lhs == Truthiness::AlwaysFalse && rhs == Truthiness::AlwaysFalse { - Truthiness::AlwaysFalse - } else if rhs == Truthiness::AlwaysTrue { + if rhs == Truthiness::AlwaysTrue { Truthiness::AlwaysTrue + } else if lhs == Truthiness::AlwaysFalse && rhs == Truthiness::AlwaysFalse { + Truthiness::AlwaysFalse } else { Truthiness::Ambiguous } From 4f6fba2cab5203be52ef4bcc66acceab2cc513f0 Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 18 Dec 2024 08:50:44 +0100 Subject: [PATCH 17/65] Introduce VisibilityConstraints struct --- .../src/semantic_index.rs | 2 +- .../src/semantic_index/builder.rs | 2 +- .../src/semantic_index/use_def.rs | 47 +++------- .../semantic_index/use_def/symbol_state.rs | 86 ++++--------------- .../semantic_index/visibility_constraint.rs | 34 -------- .../semantic_index/visibility_constraints.rs | 82 ++++++++++++++++++ .../src/types/static_visibility.rs | 7 +- 7 files changed, 118 insertions(+), 142 deletions(-) delete mode 100644 crates/red_knot_python_semantic/src/semantic_index/visibility_constraint.rs create mode 100644 crates/red_knot_python_semantic/src/semantic_index/visibility_constraints.rs diff --git a/crates/red_knot_python_semantic/src/semantic_index.rs b/crates/red_knot_python_semantic/src/semantic_index.rs index 52d7b1298fcc7..8ebdc1db59387 100644 --- a/crates/red_knot_python_semantic/src/semantic_index.rs +++ b/crates/red_knot_python_semantic/src/semantic_index.rs @@ -27,7 +27,7 @@ pub mod definition; pub mod expression; pub mod symbol; mod use_def; -pub(crate) mod visibility_constraint; +pub(crate) mod visibility_constraints; pub(crate) use self::use_def::{ BindingWithConstraints, BindingWithConstraintsIterator, DeclarationsIterator, diff --git a/crates/red_knot_python_semantic/src/semantic_index/builder.rs b/crates/red_knot_python_semantic/src/semantic_index/builder.rs index bd62633d8a349..2f7911f6cf7e8 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder.rs @@ -28,7 +28,7 @@ use crate::semantic_index::symbol::{ use crate::semantic_index::use_def::{ FlowSnapshot, ScopedConstraintId, ScopedVisibilityConstraintId, UseDefMapBuilder, }; -use crate::semantic_index::visibility_constraint::VisibilityConstraintRef; +use crate::semantic_index::visibility_constraints::VisibilityConstraintRef; use crate::semantic_index::SemanticIndex; use crate::unpack::Unpack; use crate::Db; diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index 047f75d5b2486..5068d2dfe8eca 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -229,7 +229,9 @@ pub(crate) use self::symbol_state::{ScopedConstraintId, ScopedVisibilityConstrai use crate::semantic_index::ast_ids::ScopedUseId; use crate::semantic_index::definition::Definition; use crate::semantic_index::symbol::ScopedSymbolId; -pub(crate) use crate::semantic_index::visibility_constraint::VisibilityConstraintRef; +use crate::semantic_index::visibility_constraints::{ + VisibilityConstraintRef, VisibilityConstraints, +}; use ruff_index::IndexVec; use rustc_hash::FxHashMap; @@ -248,7 +250,7 @@ pub(crate) struct UseDefMap<'db> { all_constraints: IndexVec>, /// Array of [`VisibilityConstraintRef`] in this scope. - all_visibility_constraints: IndexVec, + all_visibility_constraints: VisibilityConstraints, /// [`SymbolBindings`] reaching a [`ScopedUseId`]. bindings_by_use: IndexVec, @@ -389,8 +391,7 @@ pub(crate) struct BindingWithConstraints<'map, 'db> { pub(crate) binding: Option>, pub(crate) constraints: ConstraintsIterator<'map, 'db>, pub(crate) all_constraints: &'map IndexVec>, - pub(crate) all_visibility_constraints: - &'map IndexVec, + pub(crate) all_visibility_constraints: &'map VisibilityConstraints, pub(crate) visibility_constraint: ScopedVisibilityConstraintId, } @@ -420,7 +421,7 @@ impl<'map, 'db> Iterator for DeclarationsIterator<'map, 'db> { type Item = ( Option>, &'map IndexVec>, - &'map IndexVec, + &'map VisibilityConstraints, ScopedVisibilityConstraintId, ); @@ -456,7 +457,7 @@ pub(super) struct UseDefMapBuilder<'db> { all_constraints: IndexVec>, /// Append-only array of [`VisibilityConstraintRef`]. - all_visibility_constraints: IndexVec, + all_visibility_constraints: VisibilityConstraints, unbound_visibility_constraint_id: ScopedVisibilityConstraintId, @@ -475,7 +476,7 @@ impl<'db> UseDefMapBuilder<'db> { Self { all_definitions: IndexVec::from_iter([None]), all_constraints: IndexVec::new(), - all_visibility_constraints: IndexVec::from_iter([VisibilityConstraintRef::None]), + all_visibility_constraints: VisibilityConstraints::new(), unbound_visibility_constraint_id: ScopedVisibilityConstraintId::from_u32(0), bindings_by_use: IndexVec::new(), definitions_by_definition: FxHashMap::default(), @@ -512,7 +513,7 @@ impl<'db> UseDefMapBuilder<'db> { &mut self, constraint: VisibilityConstraintRef, ) -> ScopedVisibilityConstraintId { - self.all_visibility_constraints.push(constraint) + self.all_visibility_constraints.add(constraint) } pub(super) fn record_visibility_constraint( @@ -532,7 +533,7 @@ impl<'db> UseDefMapBuilder<'db> { } else { self.unbound_visibility_constraint_id = self.all_visibility_constraints - .push(VisibilityConstraintRef::Sequence( + .add(VisibilityConstraintRef::Sequence( self.unbound_visibility_constraint_id, new_constraint_id, )); @@ -644,31 +645,11 @@ impl<'db> UseDefMapBuilder<'db> { } // Merge unbound visibility constraints: - match ( - &self.all_visibility_constraints[self.unbound_visibility_constraint_id], - &self.all_visibility_constraints[snapshot.unbound_visibility_constraint_id], - ) { - (_, VisibilityConstraintRef::Negated(id)) - if self.unbound_visibility_constraint_id == *id => - { - self.unbound_visibility_constraint_id = ScopedVisibilityConstraintId::from_u32(0); - } - (VisibilityConstraintRef::Negated(id), _) - if *id == snapshot.unbound_visibility_constraint_id => - { - self.unbound_visibility_constraint_id = ScopedVisibilityConstraintId::from_u32(0); - } - _ => { - let constraint_id = - self.all_visibility_constraints - .push(VisibilityConstraintRef::Merged( - self.unbound_visibility_constraint_id, - snapshot.unbound_visibility_constraint_id, - )); - self.unbound_visibility_constraint_id = constraint_id; - } - } + self.unbound_visibility_constraint_id = self.all_visibility_constraints.add_merged( + self.unbound_visibility_constraint_id, + snapshot.unbound_visibility_constraint_id, + ); } pub(super) fn finish(mut self) -> UseDefMap<'db> { diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs index 4f9f9ccb3881e..7be9fe0c9d975 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs @@ -43,7 +43,7 @@ //! //! Tracking live declarations is simpler, since constraints are not involved, but otherwise very //! similar to tracking live bindings. -use crate::semantic_index::use_def::{Constraint, VisibilityConstraintRef}; +use crate::semantic_index::use_def::{Constraint, VisibilityConstraintRef, VisibilityConstraints}; use super::bitset::{BitSet, BitSetIterator}; use ruff_index::{newtype_index, IndexVec}; @@ -131,10 +131,7 @@ impl SymbolDeclarations { /// Add given visibility constraint to all live bindings. pub(super) fn record_visibility_constraint( &mut self, - all_visibility_constraints: &mut IndexVec< - ScopedVisibilityConstraintId, - VisibilityConstraintRef, - >, + all_visibility_constraints: &mut VisibilityConstraints, constraint: ScopedVisibilityConstraintId, ) { for existing in &mut self.visibility_constraints { @@ -142,7 +139,7 @@ impl SymbolDeclarations { *existing = constraint; } else { *existing = all_visibility_constraints - .push(VisibilityConstraintRef::Sequence(*existing, constraint)); + .add(VisibilityConstraintRef::Sequence(*existing, constraint)); } } } @@ -214,10 +211,7 @@ impl SymbolBindings { /// Add given visibility constraint to all live bindings. pub(super) fn record_visibility_constraint( &mut self, - all_visibility_constraints: &mut IndexVec< - ScopedVisibilityConstraintId, - VisibilityConstraintRef, - >, + all_visibility_constraints: &mut VisibilityConstraints, constraint: ScopedVisibilityConstraintId, ) { for existing in &mut self.visibility_constraints { @@ -225,7 +219,7 @@ impl SymbolBindings { *existing = constraint; } else { *existing = all_visibility_constraints - .push(VisibilityConstraintRef::Sequence(*existing, constraint)); + .add(VisibilityConstraintRef::Sequence(*existing, constraint)); } } } @@ -234,10 +228,7 @@ impl SymbolBindings { pub(super) fn iter<'map, 'db>( &'map self, all_constraints: &'map IndexVec>, - all_visibility_constraints: &'map IndexVec< - ScopedVisibilityConstraintId, - VisibilityConstraintRef, - >, + all_visibility_constraints: &'map VisibilityConstraints, ) -> BindingIdWithConstraintsIterator<'map, 'db> { BindingIdWithConstraintsIterator { all_constraints, @@ -277,10 +268,7 @@ impl SymbolState { /// Add given visibility constraint to all live bindings. pub(super) fn record_visibility_constraint( &mut self, - all_visibility_constraints: &mut IndexVec< - ScopedVisibilityConstraintId, - VisibilityConstraintRef, - >, + all_visibility_constraints: &mut VisibilityConstraints, constraint: ScopedVisibilityConstraintId, ) { self.bindings @@ -308,10 +296,7 @@ impl SymbolState { pub(super) fn merge( &mut self, b: SymbolState, - all_visibility_constraints: &mut IndexVec< - ScopedVisibilityConstraintId, - VisibilityConstraintRef, - >, + all_visibility_constraints: &mut VisibilityConstraints, ) { let mut a = Self { bindings: SymbolBindings { @@ -422,26 +407,8 @@ impl SymbolState { .next() .expect("visibility_constraints length mismatch"); let current = self.bindings.visibility_constraints.last_mut().unwrap(); - match ( - &all_visibility_constraints[*current], - &all_visibility_constraints[a_vis_constraint], - ) { - (_, VisibilityConstraintRef::Negated(id)) if current == id => { - *current = ScopedVisibilityConstraintId::from_u32(0); - } - - (VisibilityConstraintRef::Negated(id), _) - if *id == a_vis_constraint => - { - *current = ScopedVisibilityConstraintId::from_u32(0); - } - _ => { - let constraint_id = all_visibility_constraints.push( - VisibilityConstraintRef::Merged(*current, a_vis_constraint), - ); - *current = constraint_id; - } - } + *current = + all_visibility_constraints.add_merged(*current, a_vis_constraint); opt_a_def = a_defs_iter.next(); opt_b_def = b_defs_iter.next(); @@ -507,26 +474,8 @@ impl SymbolState { .next() .expect("declarations and visibility_constraints length mismatch"); let current = self.declarations.visibility_constraints.last_mut().unwrap(); - match ( - &all_visibility_constraints[*current], - &all_visibility_constraints[a_vis_constraint], - ) { - (_, VisibilityConstraintRef::Negated(id)) if current == id => { - *current = ScopedVisibilityConstraintId::from_u32(0); - } - - (VisibilityConstraintRef::Negated(id), _) - if *id == a_vis_constraint => - { - *current = ScopedVisibilityConstraintId::from_u32(0); - } - _ => { - let constraint_id = all_visibility_constraints.push( - VisibilityConstraintRef::Merged(*current, a_vis_constraint), - ); - *current = constraint_id; - } - } + *current = + all_visibility_constraints.add_merged(*current, a_vis_constraint); opt_a_decl = a_decls_iter.next(); opt_b_decl = b_decls_iter.next(); @@ -561,16 +510,14 @@ pub(super) struct BindingIdWithConstraints<'map, 'db> { pub(super) definition: ScopedDefinitionId, pub(super) constraint_ids: ConstraintIdIterator<'map>, pub(super) all_constraints: &'map IndexVec>, - pub(super) all_visibility_constraints: - &'map IndexVec, + pub(super) all_visibility_constraints: &'map VisibilityConstraints, pub(super) visibility_constraint: ScopedVisibilityConstraintId, } #[derive(Debug)] pub(super) struct BindingIdWithConstraintsIterator<'map, 'db> { all_constraints: &'map IndexVec>, - all_visibility_constraints: - &'map IndexVec, + all_visibility_constraints: &'map VisibilityConstraints, definitions: BindingsIterator<'map>, constraints: ConstraintsIterator<'map>, visibility_constraints: VisibilityConstraintsIterator<'map>, @@ -622,8 +569,7 @@ impl std::iter::FusedIterator for ConstraintIdIterator<'_> {} pub(super) struct DeclarationIdIterator<'map, 'db> { pub(crate) all_constraints: &'map IndexVec>, - pub(crate) all_visibility_constraints: - &'map IndexVec, + pub(crate) all_visibility_constraints: &'map VisibilityConstraints, pub(crate) inner: DeclarationsIterator<'map>, pub(crate) visibility_constraints: VisibilityConstraintsIterator<'map>, } @@ -632,7 +578,7 @@ impl<'map, 'db> Iterator for DeclarationIdIterator<'map, 'db> { type Item = ( ScopedDefinitionId, &'map IndexVec>, - &'map IndexVec, + &'map VisibilityConstraints, ScopedVisibilityConstraintId, ); diff --git a/crates/red_knot_python_semantic/src/semantic_index/visibility_constraint.rs b/crates/red_knot_python_semantic/src/semantic_index/visibility_constraint.rs deleted file mode 100644 index 761db197334a6..0000000000000 --- a/crates/red_knot_python_semantic/src/semantic_index/visibility_constraint.rs +++ /dev/null @@ -1,34 +0,0 @@ -use crate::semantic_index::use_def::{ScopedConstraintId, ScopedVisibilityConstraintId}; - -/// TODO -/// -/// Used to represent active branching conditions that apply to a particular definition. -/// A definition can either be conditional on a specific constraint from a `if`, `elif`, -/// `while` statement, an `if`-expression, or a Boolean expression. Or it can be marked -/// as 'ambiguous' if it occurred in a control-flow path that is not conditional on any -/// specific expression that can be statically analyzed (`for` loop, `try` ... `except`). -/// -/// -/// For example: -/// ```py -/// a = 1 # no visibility constraints -/// -/// if test1: -/// b = 1 # Constraint(test1) -/// -/// if test2: -/// c = 1 # Constraint(test1), Constraint(test2) -/// -/// for _ in range(10): -/// d = 1 # Constraint(test1), Ambiguous -/// else: -/// d = 1 # Constraint(~test1) -/// ``` -#[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) enum VisibilityConstraintRef { - None, - Single(ScopedConstraintId), - Negated(ScopedVisibilityConstraintId), - Sequence(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), - Merged(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), -} diff --git a/crates/red_knot_python_semantic/src/semantic_index/visibility_constraints.rs b/crates/red_knot_python_semantic/src/semantic_index/visibility_constraints.rs new file mode 100644 index 0000000000000..ac0bb40d34a9b --- /dev/null +++ b/crates/red_knot_python_semantic/src/semantic_index/visibility_constraints.rs @@ -0,0 +1,82 @@ +use std::ops::Index; + +use ruff_index::IndexVec; + +use crate::semantic_index::{ScopedConstraintId, ScopedVisibilityConstraintId}; + +/// TODO +/// +/// Used to represent active branching conditions that apply to a particular definition. +/// A definition can either be conditional on a specific constraint from a `if`, `elif`, +/// `while` statement, an `if`-expression, or a Boolean expression. Or it can be marked +/// as 'ambiguous' if it occurred in a control-flow path that is not conditional on any +/// specific expression that can be statically analyzed (`for` loop, `try` ... `except`). +/// +/// +/// For example: +/// ```py +/// a = 1 # no visibility constraints +/// +/// if test1: +/// b = 1 # Constraint(test1) +/// +/// if test2: +/// c = 1 # Constraint(test1), Constraint(test2) +/// +/// for _ in range(10): +/// d = 1 # Constraint(test1), Ambiguous +/// else: +/// d = 1 # Constraint(~test1) +/// ``` +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) enum VisibilityConstraintRef { + None, + Single(ScopedConstraintId), + Negated(ScopedVisibilityConstraintId), + Sequence(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), + Merged(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), +} + +#[derive(Debug, PartialEq, Eq)] +pub(crate) struct VisibilityConstraints { + constraints: IndexVec, +} + +impl VisibilityConstraints { + pub(crate) fn new() -> Self { + Self { + constraints: IndexVec::from_iter([VisibilityConstraintRef::None]), + } + } + + pub(crate) fn add( + &mut self, + constraint: VisibilityConstraintRef, + ) -> ScopedVisibilityConstraintId { + self.constraints.push(constraint) + } + + pub(crate) fn add_merged( + &mut self, + a: ScopedVisibilityConstraintId, + b: ScopedVisibilityConstraintId, + ) -> ScopedVisibilityConstraintId { + match (&self.constraints[a], &self.constraints[b]) { + (_, VisibilityConstraintRef::Negated(id)) if a == *id => { + ScopedVisibilityConstraintId::from_u32(0) + } + (VisibilityConstraintRef::Negated(id), _) if *id == b => { + ScopedVisibilityConstraintId::from_u32(0) + } + _ => self.add(VisibilityConstraintRef::Merged(a, b)), + } + } +} + +impl Index for VisibilityConstraints { + type Output = VisibilityConstraintRef; + + fn index(&self, index: ScopedVisibilityConstraintId) -> &Self::Output { + &self.constraints[index] + } +} diff --git a/crates/red_knot_python_semantic/src/types/static_visibility.rs b/crates/red_knot_python_semantic/src/types/static_visibility.rs index 456a574fd016c..acba254742916 100644 --- a/crates/red_knot_python_semantic/src/types/static_visibility.rs +++ b/crates/red_knot_python_semantic/src/types/static_visibility.rs @@ -3,7 +3,8 @@ use ruff_index::IndexVec; use crate::semantic_index::{ ast_ids::HasScopedExpressionId, constraint::{Constraint, ConstraintNode, PatternConstraintKind}, - visibility_constraint::VisibilityConstraintRef, + visibility_constraints::VisibilityConstraintRef, + visibility_constraints::VisibilityConstraints, ScopedConstraintId, ScopedVisibilityConstraintId, }; use crate::types::{infer_expression_types, Truthiness}; @@ -15,7 +16,7 @@ const MAX_RECURSION_DEPTH: usize = 10; pub(crate) fn analyze<'db>( db: &'db dyn Db, all_constraints: &IndexVec>, - all_visibility_constraints: &IndexVec, + all_visibility_constraints: &VisibilityConstraints, visibility_constraint_id: ScopedVisibilityConstraintId, ) -> Truthiness { analyze_impl( @@ -30,7 +31,7 @@ pub(crate) fn analyze<'db>( fn analyze_impl<'db>( db: &'db dyn Db, all_constraints: &IndexVec>, - all_visibility_constraints: &IndexVec, + all_visibility_constraints: &VisibilityConstraints, visibility_constraint_id: ScopedVisibilityConstraintId, max_depth: usize, ) -> Truthiness { From 5df51f26cc679c619fed6469a202b12b7b5c4c66 Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 18 Dec 2024 08:54:49 +0100 Subject: [PATCH 18/65] Minor cleanup --- .../src/types/static_visibility.rs | 48 +++++++------------ 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types/static_visibility.rs b/crates/red_knot_python_semantic/src/types/static_visibility.rs index acba254742916..bef34dad5e80a 100644 --- a/crates/red_knot_python_semantic/src/types/static_visibility.rs +++ b/crates/red_knot_python_semantic/src/types/static_visibility.rs @@ -17,32 +17,32 @@ pub(crate) fn analyze<'db>( db: &'db dyn Db, all_constraints: &IndexVec>, all_visibility_constraints: &VisibilityConstraints, - visibility_constraint_id: ScopedVisibilityConstraintId, + id: ScopedVisibilityConstraintId, ) -> Truthiness { analyze_impl( db, all_constraints, all_visibility_constraints, - visibility_constraint_id, + id, MAX_RECURSION_DEPTH, ) } fn analyze_impl<'db>( db: &'db dyn Db, - all_constraints: &IndexVec>, - all_visibility_constraints: &VisibilityConstraints, - visibility_constraint_id: ScopedVisibilityConstraintId, + constraints: &IndexVec>, + visibility_constraints: &VisibilityConstraints, + id: ScopedVisibilityConstraintId, max_depth: usize, ) -> Truthiness { if max_depth == 0 { return Truthiness::Ambiguous; } - let visibility_constraint = &all_visibility_constraints[visibility_constraint_id]; + let visibility_constraint = &visibility_constraints[id]; match visibility_constraint { VisibilityConstraintRef::Single(id) => { - let constraint = &all_constraints[*id]; + let constraint = &constraints[*id]; match constraint.node { ConstraintNode::Expression(test_expr) => { @@ -89,35 +89,23 @@ fn analyze_impl<'db>( }, } } - VisibilityConstraintRef::Negated(visibility_constraint_id) => analyze_impl( + VisibilityConstraintRef::Negated(inner_id) => analyze_impl( db, - all_constraints, - all_visibility_constraints, - *visibility_constraint_id, + constraints, + visibility_constraints, + *inner_id, max_depth - 1, ) .negate(), VisibilityConstraintRef::None => Truthiness::AlwaysTrue, - VisibilityConstraintRef::Sequence(lhs_id, rhs_id) => { - let lhs = analyze_impl( - db, - all_constraints, - all_visibility_constraints, - *lhs_id, - max_depth - 1, - ); + VisibilityConstraintRef::Sequence(lhs, rhs) => { + let lhs = analyze_impl(db, constraints, visibility_constraints, *lhs, max_depth - 1); if lhs == Truthiness::AlwaysFalse { return Truthiness::AlwaysFalse; } - let rhs = analyze_impl( - db, - all_constraints, - all_visibility_constraints, - *rhs_id, - max_depth - 1, - ); + let rhs = analyze_impl(db, constraints, visibility_constraints, *rhs, max_depth - 1); if rhs == Truthiness::AlwaysFalse { Truthiness::AlwaysFalse @@ -130,8 +118,8 @@ fn analyze_impl<'db>( VisibilityConstraintRef::Merged(lhs_id, rhs_id) => { let lhs = analyze_impl( db, - all_constraints, - all_visibility_constraints, + constraints, + visibility_constraints, *lhs_id, max_depth - 1, ); @@ -142,8 +130,8 @@ fn analyze_impl<'db>( let rhs = analyze_impl( db, - all_constraints, - all_visibility_constraints, + constraints, + visibility_constraints, *rhs_id, max_depth - 1, ); From 79b582c584c622334b48e470be73c020e0629556 Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 18 Dec 2024 09:05:48 +0100 Subject: [PATCH 19/65] Refactoring --- crates/red_knot_python_semantic/src/lib.rs | 1 + .../src/semantic_index.rs | 1 - .../src/semantic_index/builder.rs | 2 +- .../src/semantic_index/use_def.rs | 43 ++--- .../semantic_index/use_def/symbol_state.rs | 51 +++--- .../semantic_index/visibility_constraints.rs | 82 --------- crates/red_knot_python_semantic/src/types.rs | 22 +-- .../src/types/static_visibility.rs | 148 ---------------- .../src/visibility_constraints.rs | 167 ++++++++++++++++++ 9 files changed, 220 insertions(+), 297 deletions(-) delete mode 100644 crates/red_knot_python_semantic/src/semantic_index/visibility_constraints.rs delete mode 100644 crates/red_knot_python_semantic/src/types/static_visibility.rs create mode 100644 crates/red_knot_python_semantic/src/visibility_constraints.rs diff --git a/crates/red_knot_python_semantic/src/lib.rs b/crates/red_knot_python_semantic/src/lib.rs index b05a41a0ef4c2..0a83fd713995e 100644 --- a/crates/red_knot_python_semantic/src/lib.rs +++ b/crates/red_knot_python_semantic/src/lib.rs @@ -28,6 +28,7 @@ pub(crate) mod symbol; pub mod types; mod unpack; mod util; +mod visibility_constraints; type FxOrderSet = ordermap::set::OrderSet>; diff --git a/crates/red_knot_python_semantic/src/semantic_index.rs b/crates/red_knot_python_semantic/src/semantic_index.rs index 8ebdc1db59387..5633de4b751d2 100644 --- a/crates/red_knot_python_semantic/src/semantic_index.rs +++ b/crates/red_knot_python_semantic/src/semantic_index.rs @@ -27,7 +27,6 @@ pub mod definition; pub mod expression; pub mod symbol; mod use_def; -pub(crate) mod visibility_constraints; pub(crate) use self::use_def::{ BindingWithConstraints, BindingWithConstraintsIterator, DeclarationsIterator, diff --git a/crates/red_knot_python_semantic/src/semantic_index/builder.rs b/crates/red_knot_python_semantic/src/semantic_index/builder.rs index 2f7911f6cf7e8..2e4e960373ae9 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder.rs @@ -28,9 +28,9 @@ use crate::semantic_index::symbol::{ use crate::semantic_index::use_def::{ FlowSnapshot, ScopedConstraintId, ScopedVisibilityConstraintId, UseDefMapBuilder, }; -use crate::semantic_index::visibility_constraints::VisibilityConstraintRef; use crate::semantic_index::SemanticIndex; use crate::unpack::Unpack; +use crate::visibility_constraints::VisibilityConstraintRef; use crate::Db; use super::constraint::{Constraint, ConstraintNode, PatternConstraint}; diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index 5068d2dfe8eca..6fc09775564fd 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -229,9 +229,7 @@ pub(crate) use self::symbol_state::{ScopedConstraintId, ScopedVisibilityConstrai use crate::semantic_index::ast_ids::ScopedUseId; use crate::semantic_index::definition::Definition; use crate::semantic_index::symbol::ScopedSymbolId; -use crate::semantic_index::visibility_constraints::{ - VisibilityConstraintRef, VisibilityConstraints, -}; +use crate::visibility_constraints::{VisibilityConstraintRef, VisibilityConstraints}; use ruff_index::IndexVec; use rustc_hash::FxHashMap; @@ -250,7 +248,7 @@ pub(crate) struct UseDefMap<'db> { all_constraints: IndexVec>, /// Array of [`VisibilityConstraintRef`] in this scope. - all_visibility_constraints: VisibilityConstraints, + visibility_constraints: VisibilityConstraints, /// [`SymbolBindings`] reaching a [`ScopedUseId`]. bindings_by_use: IndexVec, @@ -328,7 +326,7 @@ impl<'db> UseDefMap<'db> { BindingWithConstraintsIterator { all_definitions: &self.all_definitions, all_constraints: &self.all_constraints, - inner: bindings.iter(&self.all_constraints, &self.all_visibility_constraints), + inner: bindings.iter(&self.all_constraints, &self.visibility_constraints), } } @@ -341,9 +339,9 @@ impl<'db> UseDefMap<'db> { inner: { DeclarationIdIterator { all_constraints: &self.all_constraints, - all_visibility_constraints: &self.all_visibility_constraints, - inner: declarations.live_declarations.iter(), - visibility_constraints: declarations.visibility_constraints.iter(), + visibility_constraints: &self.visibility_constraints, + declarations_iter: declarations.live_declarations.iter(), + visibility_constraints_iter: declarations.visibility_constraints.iter(), } }, } @@ -379,7 +377,7 @@ impl<'map, 'db> Iterator for BindingWithConstraintsIterator<'map, 'db> { constraint_ids: binding_id_with_constraints.constraint_ids, }, all_constraints: binding_id_with_constraints.all_constraints, - all_visibility_constraints: binding_id_with_constraints.all_visibility_constraints, + visibility_constraints: binding_id_with_constraints.visibility_constraints, visibility_constraint: binding_id_with_constraints.visibility_constraint, }) } @@ -391,7 +389,7 @@ pub(crate) struct BindingWithConstraints<'map, 'db> { pub(crate) binding: Option>, pub(crate) constraints: ConstraintsIterator<'map, 'db>, pub(crate) all_constraints: &'map IndexVec>, - pub(crate) all_visibility_constraints: &'map VisibilityConstraints, + pub(crate) visibility_constraints: &'map VisibilityConstraints, pub(crate) visibility_constraint: ScopedVisibilityConstraintId, } @@ -427,11 +425,11 @@ impl<'map, 'db> Iterator for DeclarationsIterator<'map, 'db> { fn next(&mut self) -> Option { self.inner.next().map( - |(def_id, all_constraints, all_visibility_constraints, visibility_constraint_id)| { + |(def_id, all_constraints, visibility_constraints, visibility_constraint_id)| { ( self.all_definitions[def_id], all_constraints, - all_visibility_constraints, + visibility_constraints, visibility_constraint_id, ) }, @@ -457,7 +455,7 @@ pub(super) struct UseDefMapBuilder<'db> { all_constraints: IndexVec>, /// Append-only array of [`VisibilityConstraintRef`]. - all_visibility_constraints: VisibilityConstraints, + visibility_constraints: VisibilityConstraints, unbound_visibility_constraint_id: ScopedVisibilityConstraintId, @@ -476,7 +474,7 @@ impl<'db> UseDefMapBuilder<'db> { Self { all_definitions: IndexVec::from_iter([None]), all_constraints: IndexVec::new(), - all_visibility_constraints: VisibilityConstraints::new(), + visibility_constraints: VisibilityConstraints::new(), unbound_visibility_constraint_id: ScopedVisibilityConstraintId::from_u32(0), bindings_by_use: IndexVec::new(), definitions_by_definition: FxHashMap::default(), @@ -513,7 +511,7 @@ impl<'db> UseDefMapBuilder<'db> { &mut self, constraint: VisibilityConstraintRef, ) -> ScopedVisibilityConstraintId { - self.all_visibility_constraints.add(constraint) + self.visibility_constraints.add(constraint) } pub(super) fn record_visibility_constraint( @@ -522,17 +520,14 @@ impl<'db> UseDefMapBuilder<'db> { ) -> ScopedVisibilityConstraintId { let new_constraint_id = self.add_visibility_constraint(constraint); for state in &mut self.symbol_states { - state.record_visibility_constraint( - &mut self.all_visibility_constraints, - new_constraint_id, - ); + state.record_visibility_constraint(&mut self.visibility_constraints, new_constraint_id); } if self.unbound_visibility_constraint_id == ScopedVisibilityConstraintId::from_u32(0) { self.unbound_visibility_constraint_id = new_constraint_id; } else { self.unbound_visibility_constraint_id = - self.all_visibility_constraints + self.visibility_constraints .add(VisibilityConstraintRef::Sequence( self.unbound_visibility_constraint_id, new_constraint_id, @@ -634,11 +629,11 @@ impl<'db> UseDefMapBuilder<'db> { let mut snapshot_definitions_iter = snapshot.symbol_states.into_iter(); for current in &mut self.symbol_states { if let Some(snapshot) = snapshot_definitions_iter.next() { - current.merge(snapshot, &mut self.all_visibility_constraints); + current.merge(snapshot, &mut self.visibility_constraints); } else { current.merge( SymbolState::undefined(snapshot.unbound_visibility_constraint_id), - &mut self.all_visibility_constraints, + &mut self.visibility_constraints, ); // Symbol not present in snapshot, so it's unbound/undeclared from that path. } @@ -646,7 +641,7 @@ impl<'db> UseDefMapBuilder<'db> { // Merge unbound visibility constraints: - self.unbound_visibility_constraint_id = self.all_visibility_constraints.add_merged( + self.unbound_visibility_constraint_id = self.visibility_constraints.add_merged( self.unbound_visibility_constraint_id, snapshot.unbound_visibility_constraint_id, ); @@ -662,7 +657,7 @@ impl<'db> UseDefMapBuilder<'db> { UseDefMap { all_definitions: self.all_definitions, all_constraints: self.all_constraints, - all_visibility_constraints: self.all_visibility_constraints, + visibility_constraints: self.visibility_constraints, bindings_by_use: self.bindings_by_use, public_symbols: self.symbol_states, definitions_by_definition: self.definitions_by_definition, diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs index 7be9fe0c9d975..537c4352f4201 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs @@ -131,14 +131,14 @@ impl SymbolDeclarations { /// Add given visibility constraint to all live bindings. pub(super) fn record_visibility_constraint( &mut self, - all_visibility_constraints: &mut VisibilityConstraints, + visibility_constraints: &mut VisibilityConstraints, constraint: ScopedVisibilityConstraintId, ) { for existing in &mut self.visibility_constraints { if existing == &ScopedVisibilityConstraintId::from_u32(0) { *existing = constraint; } else { - *existing = all_visibility_constraints + *existing = visibility_constraints .add(VisibilityConstraintRef::Sequence(*existing, constraint)); } } @@ -211,14 +211,14 @@ impl SymbolBindings { /// Add given visibility constraint to all live bindings. pub(super) fn record_visibility_constraint( &mut self, - all_visibility_constraints: &mut VisibilityConstraints, + visibility_constraints: &mut VisibilityConstraints, constraint: ScopedVisibilityConstraintId, ) { for existing in &mut self.visibility_constraints { if existing == &ScopedVisibilityConstraintId::from_u32(0) { *existing = constraint; } else { - *existing = all_visibility_constraints + *existing = visibility_constraints .add(VisibilityConstraintRef::Sequence(*existing, constraint)); } } @@ -228,14 +228,14 @@ impl SymbolBindings { pub(super) fn iter<'map, 'db>( &'map self, all_constraints: &'map IndexVec>, - all_visibility_constraints: &'map VisibilityConstraints, + visibility_constraints: &'map VisibilityConstraints, ) -> BindingIdWithConstraintsIterator<'map, 'db> { BindingIdWithConstraintsIterator { all_constraints, - all_visibility_constraints, + visibility_constraints, definitions: self.live_bindings.iter(), constraints: self.constraints.iter(), - visibility_constraints: self.visibility_constraints.iter(), + visibility_constraints_iter: self.visibility_constraints.iter(), } } } @@ -268,13 +268,13 @@ impl SymbolState { /// Add given visibility constraint to all live bindings. pub(super) fn record_visibility_constraint( &mut self, - all_visibility_constraints: &mut VisibilityConstraints, + visibility_constraints: &mut VisibilityConstraints, constraint: ScopedVisibilityConstraintId, ) { self.bindings - .record_visibility_constraint(all_visibility_constraints, constraint); + .record_visibility_constraint(visibility_constraints, constraint); self.declarations - .record_visibility_constraint(all_visibility_constraints, constraint); + .record_visibility_constraint(visibility_constraints, constraint); } pub(super) fn reset_visibility_constraints(&mut self, snapshot_state: SymbolState) { @@ -296,7 +296,7 @@ impl SymbolState { pub(super) fn merge( &mut self, b: SymbolState, - all_visibility_constraints: &mut VisibilityConstraints, + visibility_constraints: &mut VisibilityConstraints, ) { let mut a = Self { bindings: SymbolBindings { @@ -407,8 +407,7 @@ impl SymbolState { .next() .expect("visibility_constraints length mismatch"); let current = self.bindings.visibility_constraints.last_mut().unwrap(); - *current = - all_visibility_constraints.add_merged(*current, a_vis_constraint); + *current = visibility_constraints.add_merged(*current, a_vis_constraint); opt_a_def = a_defs_iter.next(); opt_b_def = b_defs_iter.next(); @@ -474,8 +473,7 @@ impl SymbolState { .next() .expect("declarations and visibility_constraints length mismatch"); let current = self.declarations.visibility_constraints.last_mut().unwrap(); - *current = - all_visibility_constraints.add_merged(*current, a_vis_constraint); + *current = visibility_constraints.add_merged(*current, a_vis_constraint); opt_a_decl = a_decls_iter.next(); opt_b_decl = b_decls_iter.next(); @@ -510,17 +508,17 @@ pub(super) struct BindingIdWithConstraints<'map, 'db> { pub(super) definition: ScopedDefinitionId, pub(super) constraint_ids: ConstraintIdIterator<'map>, pub(super) all_constraints: &'map IndexVec>, - pub(super) all_visibility_constraints: &'map VisibilityConstraints, + pub(super) visibility_constraints: &'map VisibilityConstraints, pub(super) visibility_constraint: ScopedVisibilityConstraintId, } #[derive(Debug)] pub(super) struct BindingIdWithConstraintsIterator<'map, 'db> { all_constraints: &'map IndexVec>, - all_visibility_constraints: &'map VisibilityConstraints, + visibility_constraints: &'map VisibilityConstraints, definitions: BindingsIterator<'map>, constraints: ConstraintsIterator<'map>, - visibility_constraints: VisibilityConstraintsIterator<'map>, + visibility_constraints_iter: VisibilityConstraintsIterator<'map>, } impl<'map, 'db> Iterator for BindingIdWithConstraintsIterator<'map, 'db> { @@ -530,7 +528,7 @@ impl<'map, 'db> Iterator for BindingIdWithConstraintsIterator<'map, 'db> { match ( self.definitions.next(), self.constraints.next(), - self.visibility_constraints.next(), + self.visibility_constraints_iter.next(), ) { (None, None, None) => None, (Some(def), Some(constraints), Some(visibility_constraint_id)) => { @@ -540,7 +538,7 @@ impl<'map, 'db> Iterator for BindingIdWithConstraintsIterator<'map, 'db> { wrapped: constraints.iter(), }, all_constraints: self.all_constraints, - all_visibility_constraints: self.all_visibility_constraints, + visibility_constraints: self.visibility_constraints, visibility_constraint: *visibility_constraint_id, }) } @@ -569,9 +567,9 @@ impl std::iter::FusedIterator for ConstraintIdIterator<'_> {} pub(super) struct DeclarationIdIterator<'map, 'db> { pub(crate) all_constraints: &'map IndexVec>, - pub(crate) all_visibility_constraints: &'map VisibilityConstraints, - pub(crate) inner: DeclarationsIterator<'map>, - pub(crate) visibility_constraints: VisibilityConstraintsIterator<'map>, + pub(crate) visibility_constraints: &'map VisibilityConstraints, + pub(crate) declarations_iter: DeclarationsIterator<'map>, + pub(crate) visibility_constraints_iter: VisibilityConstraintsIterator<'map>, } impl<'map, 'db> Iterator for DeclarationIdIterator<'map, 'db> { @@ -583,12 +581,15 @@ impl<'map, 'db> Iterator for DeclarationIdIterator<'map, 'db> { ); fn next(&mut self) -> Option { - match (self.inner.next(), self.visibility_constraints.next()) { + match ( + self.declarations_iter.next(), + self.visibility_constraints_iter.next(), + ) { (None, None) => None, (Some(declaration), Some(visibility_constraints_id)) => Some(( ScopedDefinitionId::from_u32(declaration), self.all_constraints, - self.all_visibility_constraints, + self.visibility_constraints, *visibility_constraints_id, )), // SAFETY: see above. diff --git a/crates/red_knot_python_semantic/src/semantic_index/visibility_constraints.rs b/crates/red_knot_python_semantic/src/semantic_index/visibility_constraints.rs deleted file mode 100644 index ac0bb40d34a9b..0000000000000 --- a/crates/red_knot_python_semantic/src/semantic_index/visibility_constraints.rs +++ /dev/null @@ -1,82 +0,0 @@ -use std::ops::Index; - -use ruff_index::IndexVec; - -use crate::semantic_index::{ScopedConstraintId, ScopedVisibilityConstraintId}; - -/// TODO -/// -/// Used to represent active branching conditions that apply to a particular definition. -/// A definition can either be conditional on a specific constraint from a `if`, `elif`, -/// `while` statement, an `if`-expression, or a Boolean expression. Or it can be marked -/// as 'ambiguous' if it occurred in a control-flow path that is not conditional on any -/// specific expression that can be statically analyzed (`for` loop, `try` ... `except`). -/// -/// -/// For example: -/// ```py -/// a = 1 # no visibility constraints -/// -/// if test1: -/// b = 1 # Constraint(test1) -/// -/// if test2: -/// c = 1 # Constraint(test1), Constraint(test2) -/// -/// for _ in range(10): -/// d = 1 # Constraint(test1), Ambiguous -/// else: -/// d = 1 # Constraint(~test1) -/// ``` -#[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) enum VisibilityConstraintRef { - None, - Single(ScopedConstraintId), - Negated(ScopedVisibilityConstraintId), - Sequence(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), - Merged(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), -} - -#[derive(Debug, PartialEq, Eq)] -pub(crate) struct VisibilityConstraints { - constraints: IndexVec, -} - -impl VisibilityConstraints { - pub(crate) fn new() -> Self { - Self { - constraints: IndexVec::from_iter([VisibilityConstraintRef::None]), - } - } - - pub(crate) fn add( - &mut self, - constraint: VisibilityConstraintRef, - ) -> ScopedVisibilityConstraintId { - self.constraints.push(constraint) - } - - pub(crate) fn add_merged( - &mut self, - a: ScopedVisibilityConstraintId, - b: ScopedVisibilityConstraintId, - ) -> ScopedVisibilityConstraintId { - match (&self.constraints[a], &self.constraints[b]) { - (_, VisibilityConstraintRef::Negated(id)) if a == *id => { - ScopedVisibilityConstraintId::from_u32(0) - } - (VisibilityConstraintRef::Negated(id), _) if *id == b => { - ScopedVisibilityConstraintId::from_u32(0) - } - _ => self.add(VisibilityConstraintRef::Merged(a, b)), - } - } -} - -impl Index for VisibilityConstraints { - type Output = VisibilityConstraintRef; - - fn index(&self, index: ScopedVisibilityConstraintId) -> &Self::Output { - &self.constraints[index] - } -} diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 207fb644f1340..5e0fd81b8f8ac 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -33,7 +33,6 @@ use crate::types::diagnostic::INVALID_TYPE_FORM; use crate::types::mro::{Mro, MroError, MroIterator}; use crate::types::narrow::narrowing_constraint; use crate::{Db, FxOrderSet, Module, Program, PythonVersion}; -pub(crate) use static_visibility::analyze; mod builder; mod call; @@ -45,7 +44,6 @@ mod infer; mod mro; mod narrow; mod signatures; -mod static_visibility; mod string_annotation; mod unpacker; @@ -275,16 +273,12 @@ fn bindings_ty<'db>( binding, constraints, all_constraints, - all_visibility_constraints, + visibility_constraints, visibility_constraint, } in bindings_with_constraints { - let static_visibility = analyze( - db, - all_constraints, - all_visibility_constraints, - visibility_constraint, - ); + let static_visibility = + visibility_constraints.analyze(db, all_constraints, visibility_constraint); let Some(binding) = binding else { if !static_visibility.is_always_false() { @@ -363,15 +357,11 @@ fn declarations_ty<'db>( let mut is_unbound_visible = false; let mut types = vec![]; - for (declaration, all_constraints, all_visibility_constraints, visibility_constraint) in + for (declaration, all_constraints, visibility_constraints, visibility_constraint) in declarations { - let static_visibility = analyze( - db, - all_constraints, - all_visibility_constraints, - visibility_constraint, - ); + let static_visibility = + visibility_constraints.analyze(db, all_constraints, visibility_constraint); let Some(declaration) = declaration else { if !static_visibility.is_always_false() { diff --git a/crates/red_knot_python_semantic/src/types/static_visibility.rs b/crates/red_knot_python_semantic/src/types/static_visibility.rs deleted file mode 100644 index bef34dad5e80a..0000000000000 --- a/crates/red_knot_python_semantic/src/types/static_visibility.rs +++ /dev/null @@ -1,148 +0,0 @@ -use ruff_index::IndexVec; - -use crate::semantic_index::{ - ast_ids::HasScopedExpressionId, - constraint::{Constraint, ConstraintNode, PatternConstraintKind}, - visibility_constraints::VisibilityConstraintRef, - visibility_constraints::VisibilityConstraints, - ScopedConstraintId, ScopedVisibilityConstraintId, -}; -use crate::types::{infer_expression_types, Truthiness}; -use crate::Db; - -const MAX_RECURSION_DEPTH: usize = 10; - -/// Analyze the statically known visibility for a given visibility constraint. -pub(crate) fn analyze<'db>( - db: &'db dyn Db, - all_constraints: &IndexVec>, - all_visibility_constraints: &VisibilityConstraints, - id: ScopedVisibilityConstraintId, -) -> Truthiness { - analyze_impl( - db, - all_constraints, - all_visibility_constraints, - id, - MAX_RECURSION_DEPTH, - ) -} - -fn analyze_impl<'db>( - db: &'db dyn Db, - constraints: &IndexVec>, - visibility_constraints: &VisibilityConstraints, - id: ScopedVisibilityConstraintId, - max_depth: usize, -) -> Truthiness { - if max_depth == 0 { - return Truthiness::Ambiguous; - } - - let visibility_constraint = &visibility_constraints[id]; - match visibility_constraint { - VisibilityConstraintRef::Single(id) => { - let constraint = &constraints[*id]; - - match constraint.node { - ConstraintNode::Expression(test_expr) => { - let inference = infer_expression_types(db, test_expr); - let scope = test_expr.scope(db); - let ty = inference - .expression_ty(test_expr.node_ref(db).scoped_expression_id(db, scope)); - - ty.bool(db).negate_if(!constraint.is_positive) - } - ConstraintNode::Pattern(inner) => match inner.kind(db) { - PatternConstraintKind::Value(value, guard) => { - let subject_expression = inner.subject(db); - let inference = infer_expression_types(db, *subject_expression); - let scope = subject_expression.scope(db); - let subject_ty = inference.expression_ty( - subject_expression - .node_ref(db) - .scoped_expression_id(db, scope), - ); - - let inference = infer_expression_types(db, *value); - let scope = value.scope(db); - let value_ty = inference - .expression_ty(value.node_ref(db).scoped_expression_id(db, scope)); - - if subject_ty.is_single_valued(db) { - let truthiness = - Truthiness::from(subject_ty.is_equivalent_to(db, value_ty)); - - if truthiness.is_always_true() && guard.is_some() { - // Fall back to ambiguous, the guard might change the result. - Truthiness::Ambiguous - } else { - truthiness - } - } else { - Truthiness::Ambiguous - } - } - PatternConstraintKind::Singleton(..) | PatternConstraintKind::Unsupported => { - Truthiness::Ambiguous - } - }, - } - } - VisibilityConstraintRef::Negated(inner_id) => analyze_impl( - db, - constraints, - visibility_constraints, - *inner_id, - max_depth - 1, - ) - .negate(), - VisibilityConstraintRef::None => Truthiness::AlwaysTrue, - VisibilityConstraintRef::Sequence(lhs, rhs) => { - let lhs = analyze_impl(db, constraints, visibility_constraints, *lhs, max_depth - 1); - - if lhs == Truthiness::AlwaysFalse { - return Truthiness::AlwaysFalse; - } - - let rhs = analyze_impl(db, constraints, visibility_constraints, *rhs, max_depth - 1); - - if rhs == Truthiness::AlwaysFalse { - Truthiness::AlwaysFalse - } else if lhs == Truthiness::AlwaysTrue && rhs == Truthiness::AlwaysTrue { - Truthiness::AlwaysTrue - } else { - Truthiness::Ambiguous - } - } - VisibilityConstraintRef::Merged(lhs_id, rhs_id) => { - let lhs = analyze_impl( - db, - constraints, - visibility_constraints, - *lhs_id, - max_depth - 1, - ); - - if lhs == Truthiness::AlwaysTrue { - return Truthiness::AlwaysTrue; - } - - let rhs = analyze_impl( - db, - constraints, - visibility_constraints, - *rhs_id, - max_depth - 1, - ); - - if rhs == Truthiness::AlwaysTrue { - Truthiness::AlwaysTrue - } else if lhs == Truthiness::AlwaysFalse && rhs == Truthiness::AlwaysFalse { - Truthiness::AlwaysFalse - } else { - Truthiness::Ambiguous - } - } - } -} diff --git a/crates/red_knot_python_semantic/src/visibility_constraints.rs b/crates/red_knot_python_semantic/src/visibility_constraints.rs new file mode 100644 index 0000000000000..cafb9f457bf62 --- /dev/null +++ b/crates/red_knot_python_semantic/src/visibility_constraints.rs @@ -0,0 +1,167 @@ +use ruff_index::IndexVec; + +use crate::semantic_index::{ + ast_ids::HasScopedExpressionId, + constraint::{Constraint, ConstraintNode, PatternConstraintKind}, +}; +use crate::semantic_index::{ScopedConstraintId, ScopedVisibilityConstraintId}; +use crate::types::{infer_expression_types, Truthiness}; +use crate::Db; + +const MAX_RECURSION_DEPTH: usize = 10; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) enum VisibilityConstraintRef { + None, + Single(ScopedConstraintId), + Negated(ScopedVisibilityConstraintId), + Sequence(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), + Merged(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), +} + +#[derive(Debug, PartialEq, Eq)] +pub(crate) struct VisibilityConstraints { + constraints: IndexVec, +} + +impl VisibilityConstraints { + pub(crate) fn new() -> Self { + Self { + constraints: IndexVec::from_iter([VisibilityConstraintRef::None]), + } + } + + pub(crate) fn add( + &mut self, + constraint: VisibilityConstraintRef, + ) -> ScopedVisibilityConstraintId { + self.constraints.push(constraint) + } + + pub(crate) fn add_merged( + &mut self, + a: ScopedVisibilityConstraintId, + b: ScopedVisibilityConstraintId, + ) -> ScopedVisibilityConstraintId { + match (&self.constraints[a], &self.constraints[b]) { + (_, VisibilityConstraintRef::Negated(id)) if a == *id => { + ScopedVisibilityConstraintId::from_u32(0) + } + (VisibilityConstraintRef::Negated(id), _) if *id == b => { + ScopedVisibilityConstraintId::from_u32(0) + } + _ => self.add(VisibilityConstraintRef::Merged(a, b)), + } + } + + /// Analyze the statically known visibility for a given visibility constraint. + pub(crate) fn analyze<'db>( + self: &VisibilityConstraints, + db: &'db dyn Db, + all_constraints: &IndexVec>, + id: ScopedVisibilityConstraintId, + ) -> Truthiness { + self.analyze_impl(db, all_constraints, id, MAX_RECURSION_DEPTH) + } + + fn analyze_impl<'db>( + self: &VisibilityConstraints, + db: &'db dyn Db, + constraints: &IndexVec>, + id: ScopedVisibilityConstraintId, + max_depth: usize, + ) -> Truthiness { + if max_depth == 0 { + return Truthiness::Ambiguous; + } + + let visibility_constraint = &self.constraints[id]; + match visibility_constraint { + VisibilityConstraintRef::Single(id) => { + let constraint = &constraints[*id]; + + match constraint.node { + ConstraintNode::Expression(test_expr) => { + let inference = infer_expression_types(db, test_expr); + let scope = test_expr.scope(db); + let ty = inference + .expression_ty(test_expr.node_ref(db).scoped_expression_id(db, scope)); + + ty.bool(db).negate_if(!constraint.is_positive) + } + ConstraintNode::Pattern(inner) => match inner.kind(db) { + PatternConstraintKind::Value(value, guard) => { + let subject_expression = inner.subject(db); + let inference = infer_expression_types(db, *subject_expression); + let scope = subject_expression.scope(db); + let subject_ty = inference.expression_ty( + subject_expression + .node_ref(db) + .scoped_expression_id(db, scope), + ); + + let inference = infer_expression_types(db, *value); + let scope = value.scope(db); + let value_ty = inference + .expression_ty(value.node_ref(db).scoped_expression_id(db, scope)); + + if subject_ty.is_single_valued(db) { + let truthiness = + Truthiness::from(subject_ty.is_equivalent_to(db, value_ty)); + + if truthiness.is_always_true() && guard.is_some() { + // Fall back to ambiguous, the guard might change the result. + Truthiness::Ambiguous + } else { + truthiness + } + } else { + Truthiness::Ambiguous + } + } + PatternConstraintKind::Singleton(..) + | PatternConstraintKind::Unsupported => Truthiness::Ambiguous, + }, + } + } + VisibilityConstraintRef::Negated(inner_id) => self + .analyze_impl(db, constraints, *inner_id, max_depth - 1) + .negate(), + VisibilityConstraintRef::None => Truthiness::AlwaysTrue, + VisibilityConstraintRef::Sequence(lhs, rhs) => { + let lhs = self.analyze_impl(db, constraints, *lhs, max_depth - 1); + + if lhs == Truthiness::AlwaysFalse { + return Truthiness::AlwaysFalse; + } + + let rhs = self.analyze_impl(db, constraints, *rhs, max_depth - 1); + + if rhs == Truthiness::AlwaysFalse { + Truthiness::AlwaysFalse + } else if lhs == Truthiness::AlwaysTrue && rhs == Truthiness::AlwaysTrue { + Truthiness::AlwaysTrue + } else { + Truthiness::Ambiguous + } + } + VisibilityConstraintRef::Merged(lhs_id, rhs_id) => { + let lhs = self.analyze_impl(db, constraints, *lhs_id, max_depth - 1); + + if lhs == Truthiness::AlwaysTrue { + return Truthiness::AlwaysTrue; + } + + let rhs = self.analyze_impl(db, constraints, *rhs_id, max_depth - 1); + + if rhs == Truthiness::AlwaysTrue { + Truthiness::AlwaysTrue + } else if lhs == Truthiness::AlwaysFalse && rhs == Truthiness::AlwaysFalse { + Truthiness::AlwaysFalse + } else { + Truthiness::Ambiguous + } + } + } + } +} From afe1572d7f2a9a326e8b8c8278558fe0efd90d9c Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 18 Dec 2024 09:06:21 +0100 Subject: [PATCH 20/65] Rename --- .../src/semantic_index/builder.rs | 8 +++--- .../src/semantic_index/use_def.rs | 8 +++--- .../semantic_index/use_def/symbol_state.rs | 6 ++--- .../src/visibility_constraints.rs | 27 +++++++++---------- 4 files changed, 23 insertions(+), 26 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index/builder.rs b/crates/red_knot_python_semantic/src/semantic_index/builder.rs index 2e4e960373ae9..d1154ca14ec46 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder.rs @@ -30,7 +30,7 @@ use crate::semantic_index::use_def::{ }; use crate::semantic_index::SemanticIndex; use crate::unpack::Unpack; -use crate::visibility_constraints::VisibilityConstraintRef; +use crate::visibility_constraints::VisibilityConstraint; use crate::Db; use super::constraint::{Constraint, ConstraintNode, PatternConstraint}; @@ -301,7 +301,7 @@ impl<'db> SemanticIndexBuilder<'db> { constraint: ScopedConstraintId, ) -> ScopedVisibilityConstraintId { self.current_use_def_map_mut() - .add_visibility_constraint(VisibilityConstraintRef::Single(constraint)) + .add_visibility_constraint(VisibilityConstraint::Single(constraint)) } fn record_visibility_constraint( @@ -309,7 +309,7 @@ impl<'db> SemanticIndexBuilder<'db> { constraint: ScopedConstraintId, ) -> ScopedVisibilityConstraintId { self.current_use_def_map_mut() - .record_visibility_constraint(VisibilityConstraintRef::Single(constraint)) + .record_visibility_constraint(VisibilityConstraint::Single(constraint)) } fn reset_visibility_constraints(&mut self, snapshot: FlowSnapshot) { @@ -322,7 +322,7 @@ impl<'db> SemanticIndexBuilder<'db> { constraint: ScopedVisibilityConstraintId, ) -> ScopedVisibilityConstraintId { self.current_use_def_map_mut() - .record_visibility_constraint(VisibilityConstraintRef::Negated(constraint)) + .record_visibility_constraint(VisibilityConstraint::Negated(constraint)) } fn build_constraint(&mut self, constraint_node: &Expr) -> Constraint<'db> { diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index 6fc09775564fd..3a139798aa1e4 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -229,7 +229,7 @@ pub(crate) use self::symbol_state::{ScopedConstraintId, ScopedVisibilityConstrai use crate::semantic_index::ast_ids::ScopedUseId; use crate::semantic_index::definition::Definition; use crate::semantic_index::symbol::ScopedSymbolId; -use crate::visibility_constraints::{VisibilityConstraintRef, VisibilityConstraints}; +use crate::visibility_constraints::{VisibilityConstraint, VisibilityConstraints}; use ruff_index::IndexVec; use rustc_hash::FxHashMap; @@ -509,14 +509,14 @@ impl<'db> UseDefMapBuilder<'db> { pub(super) fn add_visibility_constraint( &mut self, - constraint: VisibilityConstraintRef, + constraint: VisibilityConstraint, ) -> ScopedVisibilityConstraintId { self.visibility_constraints.add(constraint) } pub(super) fn record_visibility_constraint( &mut self, - constraint: VisibilityConstraintRef, + constraint: VisibilityConstraint, ) -> ScopedVisibilityConstraintId { let new_constraint_id = self.add_visibility_constraint(constraint); for state in &mut self.symbol_states { @@ -528,7 +528,7 @@ impl<'db> UseDefMapBuilder<'db> { } else { self.unbound_visibility_constraint_id = self.visibility_constraints - .add(VisibilityConstraintRef::Sequence( + .add(VisibilityConstraint::Sequence( self.unbound_visibility_constraint_id, new_constraint_id, )); diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs index 537c4352f4201..82e2112094a05 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs @@ -43,7 +43,7 @@ //! //! Tracking live declarations is simpler, since constraints are not involved, but otherwise very //! similar to tracking live bindings. -use crate::semantic_index::use_def::{Constraint, VisibilityConstraintRef, VisibilityConstraints}; +use crate::semantic_index::use_def::{Constraint, VisibilityConstraint, VisibilityConstraints}; use super::bitset::{BitSet, BitSetIterator}; use ruff_index::{newtype_index, IndexVec}; @@ -139,7 +139,7 @@ impl SymbolDeclarations { *existing = constraint; } else { *existing = visibility_constraints - .add(VisibilityConstraintRef::Sequence(*existing, constraint)); + .add(VisibilityConstraint::Sequence(*existing, constraint)); } } } @@ -219,7 +219,7 @@ impl SymbolBindings { *existing = constraint; } else { *existing = visibility_constraints - .add(VisibilityConstraintRef::Sequence(*existing, constraint)); + .add(VisibilityConstraint::Sequence(*existing, constraint)); } } } diff --git a/crates/red_knot_python_semantic/src/visibility_constraints.rs b/crates/red_knot_python_semantic/src/visibility_constraints.rs index cafb9f457bf62..80e4d9bf79e19 100644 --- a/crates/red_knot_python_semantic/src/visibility_constraints.rs +++ b/crates/red_knot_python_semantic/src/visibility_constraints.rs @@ -11,7 +11,7 @@ use crate::Db; const MAX_RECURSION_DEPTH: usize = 10; #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) enum VisibilityConstraintRef { +pub(crate) enum VisibilityConstraint { None, Single(ScopedConstraintId), Negated(ScopedVisibilityConstraintId), @@ -21,20 +21,17 @@ pub(crate) enum VisibilityConstraintRef { #[derive(Debug, PartialEq, Eq)] pub(crate) struct VisibilityConstraints { - constraints: IndexVec, + constraints: IndexVec, } impl VisibilityConstraints { pub(crate) fn new() -> Self { Self { - constraints: IndexVec::from_iter([VisibilityConstraintRef::None]), + constraints: IndexVec::from_iter([VisibilityConstraint::None]), } } - pub(crate) fn add( - &mut self, - constraint: VisibilityConstraintRef, - ) -> ScopedVisibilityConstraintId { + pub(crate) fn add(&mut self, constraint: VisibilityConstraint) -> ScopedVisibilityConstraintId { self.constraints.push(constraint) } @@ -44,13 +41,13 @@ impl VisibilityConstraints { b: ScopedVisibilityConstraintId, ) -> ScopedVisibilityConstraintId { match (&self.constraints[a], &self.constraints[b]) { - (_, VisibilityConstraintRef::Negated(id)) if a == *id => { + (_, VisibilityConstraint::Negated(id)) if a == *id => { ScopedVisibilityConstraintId::from_u32(0) } - (VisibilityConstraintRef::Negated(id), _) if *id == b => { + (VisibilityConstraint::Negated(id), _) if *id == b => { ScopedVisibilityConstraintId::from_u32(0) } - _ => self.add(VisibilityConstraintRef::Merged(a, b)), + _ => self.add(VisibilityConstraint::Merged(a, b)), } } @@ -77,7 +74,7 @@ impl VisibilityConstraints { let visibility_constraint = &self.constraints[id]; match visibility_constraint { - VisibilityConstraintRef::Single(id) => { + VisibilityConstraint::Single(id) => { let constraint = &constraints[*id]; match constraint.node { @@ -124,11 +121,11 @@ impl VisibilityConstraints { }, } } - VisibilityConstraintRef::Negated(inner_id) => self + VisibilityConstraint::Negated(inner_id) => self .analyze_impl(db, constraints, *inner_id, max_depth - 1) .negate(), - VisibilityConstraintRef::None => Truthiness::AlwaysTrue, - VisibilityConstraintRef::Sequence(lhs, rhs) => { + VisibilityConstraint::None => Truthiness::AlwaysTrue, + VisibilityConstraint::Sequence(lhs, rhs) => { let lhs = self.analyze_impl(db, constraints, *lhs, max_depth - 1); if lhs == Truthiness::AlwaysFalse { @@ -145,7 +142,7 @@ impl VisibilityConstraints { Truthiness::Ambiguous } } - VisibilityConstraintRef::Merged(lhs_id, rhs_id) => { + VisibilityConstraint::Merged(lhs_id, rhs_id) => { let lhs = self.analyze_impl(db, constraints, *lhs_id, max_depth - 1); if lhs == Truthiness::AlwaysTrue { From 853e171ed12a61480fae1c1a5c9ae635e85ccbcd Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 18 Dec 2024 09:29:35 +0100 Subject: [PATCH 21/65] Further cleanup --- .../src/semantic_index/builder.rs | 6 +- .../src/semantic_index/use_def.rs | 13 +- .../semantic_index/use_def/symbol_state.rs | 16 +-- .../src/visibility_constraints.rs | 125 ++++++++++-------- 4 files changed, 79 insertions(+), 81 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index/builder.rs b/crates/red_knot_python_semantic/src/semantic_index/builder.rs index d1154ca14ec46..9fb3fae453ca3 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder.rs @@ -301,7 +301,7 @@ impl<'db> SemanticIndexBuilder<'db> { constraint: ScopedConstraintId, ) -> ScopedVisibilityConstraintId { self.current_use_def_map_mut() - .add_visibility_constraint(VisibilityConstraint::Single(constraint)) + .add_visibility_constraint(VisibilityConstraint::VisibleIf(constraint)) } fn record_visibility_constraint( @@ -309,7 +309,7 @@ impl<'db> SemanticIndexBuilder<'db> { constraint: ScopedConstraintId, ) -> ScopedVisibilityConstraintId { self.current_use_def_map_mut() - .record_visibility_constraint(VisibilityConstraint::Single(constraint)) + .record_visibility_constraint(VisibilityConstraint::VisibleIf(constraint)) } fn reset_visibility_constraints(&mut self, snapshot: FlowSnapshot) { @@ -322,7 +322,7 @@ impl<'db> SemanticIndexBuilder<'db> { constraint: ScopedVisibilityConstraintId, ) -> ScopedVisibilityConstraintId { self.current_use_def_map_mut() - .record_visibility_constraint(VisibilityConstraint::Negated(constraint)) + .record_visibility_constraint(VisibilityConstraint::VisibleIfNot(constraint)) } fn build_constraint(&mut self, constraint_node: &Expr) -> Constraint<'db> { diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index 3a139798aa1e4..cf52304138e3e 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -523,16 +523,9 @@ impl<'db> UseDefMapBuilder<'db> { state.record_visibility_constraint(&mut self.visibility_constraints, new_constraint_id); } - if self.unbound_visibility_constraint_id == ScopedVisibilityConstraintId::from_u32(0) { - self.unbound_visibility_constraint_id = new_constraint_id; - } else { - self.unbound_visibility_constraint_id = - self.visibility_constraints - .add(VisibilityConstraint::Sequence( - self.unbound_visibility_constraint_id, - new_constraint_id, - )); - } + self.unbound_visibility_constraint_id = self + .visibility_constraints + .add_sequence(self.unbound_visibility_constraint_id, new_constraint_id); new_constraint_id } diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs index 82e2112094a05..67b49d10ea351 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs @@ -43,7 +43,7 @@ //! //! Tracking live declarations is simpler, since constraints are not involved, but otherwise very //! similar to tracking live bindings. -use crate::semantic_index::use_def::{Constraint, VisibilityConstraint, VisibilityConstraints}; +use crate::semantic_index::use_def::{Constraint, VisibilityConstraints}; use super::bitset::{BitSet, BitSetIterator}; use ruff_index::{newtype_index, IndexVec}; @@ -135,12 +135,7 @@ impl SymbolDeclarations { constraint: ScopedVisibilityConstraintId, ) { for existing in &mut self.visibility_constraints { - if existing == &ScopedVisibilityConstraintId::from_u32(0) { - *existing = constraint; - } else { - *existing = visibility_constraints - .add(VisibilityConstraint::Sequence(*existing, constraint)); - } + *existing = visibility_constraints.add_sequence(*existing, constraint); } } @@ -215,12 +210,7 @@ impl SymbolBindings { constraint: ScopedVisibilityConstraintId, ) { for existing in &mut self.visibility_constraints { - if existing == &ScopedVisibilityConstraintId::from_u32(0) { - *existing = constraint; - } else { - *existing = visibility_constraints - .add(VisibilityConstraint::Sequence(*existing, constraint)); - } + *existing = visibility_constraints.add_sequence(*existing, constraint); } } diff --git a/crates/red_knot_python_semantic/src/visibility_constraints.rs b/crates/red_knot_python_semantic/src/visibility_constraints.rs index 80e4d9bf79e19..24efa53b5d9f3 100644 --- a/crates/red_knot_python_semantic/src/visibility_constraints.rs +++ b/crates/red_knot_python_semantic/src/visibility_constraints.rs @@ -12,9 +12,9 @@ const MAX_RECURSION_DEPTH: usize = 10; #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum VisibilityConstraint { - None, - Single(ScopedConstraintId), - Negated(ScopedVisibilityConstraintId), + AlwaysTrue, + VisibleIf(ScopedConstraintId), + VisibleIfNot(ScopedVisibilityConstraintId), Sequence(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), Merged(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), } @@ -27,7 +27,7 @@ pub(crate) struct VisibilityConstraints { impl VisibilityConstraints { pub(crate) fn new() -> Self { Self { - constraints: IndexVec::from_iter([VisibilityConstraint::None]), + constraints: IndexVec::from_iter([VisibilityConstraint::AlwaysTrue]), } } @@ -41,16 +41,28 @@ impl VisibilityConstraints { b: ScopedVisibilityConstraintId, ) -> ScopedVisibilityConstraintId { match (&self.constraints[a], &self.constraints[b]) { - (_, VisibilityConstraint::Negated(id)) if a == *id => { + (_, VisibilityConstraint::VisibleIfNot(id)) if a == *id => { ScopedVisibilityConstraintId::from_u32(0) } - (VisibilityConstraint::Negated(id), _) if *id == b => { + (VisibilityConstraint::VisibleIfNot(id), _) if *id == b => { ScopedVisibilityConstraintId::from_u32(0) } _ => self.add(VisibilityConstraint::Merged(a, b)), } } + pub(crate) fn add_sequence( + &mut self, + a: ScopedVisibilityConstraintId, + b: ScopedVisibilityConstraintId, + ) -> ScopedVisibilityConstraintId { + if a == ScopedVisibilityConstraintId::from_u32(0) { + b + } else { + self.add(VisibilityConstraint::Sequence(a, b)) + } + } + /// Analyze the statically known visibility for a given visibility constraint. pub(crate) fn analyze<'db>( self: &VisibilityConstraints, @@ -74,57 +86,13 @@ impl VisibilityConstraints { let visibility_constraint = &self.constraints[id]; match visibility_constraint { - VisibilityConstraint::Single(id) => { - let constraint = &constraints[*id]; - - match constraint.node { - ConstraintNode::Expression(test_expr) => { - let inference = infer_expression_types(db, test_expr); - let scope = test_expr.scope(db); - let ty = inference - .expression_ty(test_expr.node_ref(db).scoped_expression_id(db, scope)); - - ty.bool(db).negate_if(!constraint.is_positive) - } - ConstraintNode::Pattern(inner) => match inner.kind(db) { - PatternConstraintKind::Value(value, guard) => { - let subject_expression = inner.subject(db); - let inference = infer_expression_types(db, *subject_expression); - let scope = subject_expression.scope(db); - let subject_ty = inference.expression_ty( - subject_expression - .node_ref(db) - .scoped_expression_id(db, scope), - ); - - let inference = infer_expression_types(db, *value); - let scope = value.scope(db); - let value_ty = inference - .expression_ty(value.node_ref(db).scoped_expression_id(db, scope)); - - if subject_ty.is_single_valued(db) { - let truthiness = - Truthiness::from(subject_ty.is_equivalent_to(db, value_ty)); - - if truthiness.is_always_true() && guard.is_some() { - // Fall back to ambiguous, the guard might change the result. - Truthiness::Ambiguous - } else { - truthiness - } - } else { - Truthiness::Ambiguous - } - } - PatternConstraintKind::Singleton(..) - | PatternConstraintKind::Unsupported => Truthiness::Ambiguous, - }, - } + VisibilityConstraint::AlwaysTrue => Truthiness::AlwaysTrue, + VisibilityConstraint::VisibleIf(single) => { + Self::analyze_single(db, &constraints[*single]) } - VisibilityConstraint::Negated(inner_id) => self - .analyze_impl(db, constraints, *inner_id, max_depth - 1) + VisibilityConstraint::VisibleIfNot(negated) => self + .analyze_impl(db, constraints, *negated, max_depth - 1) .negate(), - VisibilityConstraint::None => Truthiness::AlwaysTrue, VisibilityConstraint::Sequence(lhs, rhs) => { let lhs = self.analyze_impl(db, constraints, *lhs, max_depth - 1); @@ -161,4 +129,51 @@ impl VisibilityConstraints { } } } + + fn analyze_single(db: &dyn Db, constraint: &Constraint) -> Truthiness { + match constraint.node { + ConstraintNode::Expression(test_expr) => { + let inference = infer_expression_types(db, test_expr); + let scope = test_expr.scope(db); + let ty = + inference.expression_ty(test_expr.node_ref(db).scoped_expression_id(db, scope)); + + ty.bool(db).negate_if(!constraint.is_positive) + } + ConstraintNode::Pattern(inner) => match inner.kind(db) { + PatternConstraintKind::Value(value, guard) => { + let subject_expression = inner.subject(db); + let inference = infer_expression_types(db, *subject_expression); + let scope = subject_expression.scope(db); + let subject_ty = inference.expression_ty( + subject_expression + .node_ref(db) + .scoped_expression_id(db, scope), + ); + + let inference = infer_expression_types(db, *value); + let scope = value.scope(db); + let value_ty = + inference.expression_ty(value.node_ref(db).scoped_expression_id(db, scope)); + + if subject_ty.is_single_valued(db) { + let truthiness = + Truthiness::from(subject_ty.is_equivalent_to(db, value_ty)); + + if truthiness.is_always_true() && guard.is_some() { + // Fall back to ambiguous, the guard might change the result. + Truthiness::Ambiguous + } else { + truthiness + } + } else { + Truthiness::Ambiguous + } + } + PatternConstraintKind::Singleton(..) | PatternConstraintKind::Unsupported => { + Truthiness::Ambiguous + } + }, + } + } } From 2e6f7574561a9e4e2bda8e777e265ea735a1a340 Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 18 Dec 2024 09:37:14 +0100 Subject: [PATCH 22/65] Rename, comment --- .../src/semantic_index/use_def.rs | 41 +++++++++---------- .../semantic_index/use_def/symbol_state.rs | 16 ++++---- 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index cf52304138e3e..d0566605321fe 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -443,7 +443,7 @@ impl std::iter::FusedIterator for DeclarationsIterator<'_, '_> {} #[derive(Clone, Debug)] pub(super) struct FlowSnapshot { symbol_states: IndexVec, - unbound_visibility_constraint_id: ScopedVisibilityConstraintId, + unbound_visibility: ScopedVisibilityConstraintId, } #[derive(Debug)] @@ -457,7 +457,9 @@ pub(super) struct UseDefMapBuilder<'db> { /// Append-only array of [`VisibilityConstraintRef`]. visibility_constraints: VisibilityConstraints, - unbound_visibility_constraint_id: ScopedVisibilityConstraintId, + /// A constraint which describes the visibility of the unbound/undeclared state, i.e. + /// whether or not the start of the scope is visible. + unbound_visibility: ScopedVisibilityConstraintId, /// Live bindings at each so-far-recorded use. bindings_by_use: IndexVec, @@ -475,7 +477,7 @@ impl<'db> UseDefMapBuilder<'db> { all_definitions: IndexVec::from_iter([None]), all_constraints: IndexVec::new(), visibility_constraints: VisibilityConstraints::new(), - unbound_visibility_constraint_id: ScopedVisibilityConstraintId::from_u32(0), + unbound_visibility: ScopedVisibilityConstraintId::from_u32(0), bindings_by_use: IndexVec::new(), definitions_by_definition: FxHashMap::default(), symbol_states: IndexVec::new(), @@ -483,9 +485,9 @@ impl<'db> UseDefMapBuilder<'db> { } pub(super) fn add_symbol(&mut self, symbol: ScopedSymbolId) { - let new_symbol = self.symbol_states.push(SymbolState::undefined( - self.unbound_visibility_constraint_id, - )); + let new_symbol = self + .symbol_states + .push(SymbolState::undefined(self.unbound_visibility)); debug_assert_eq!(symbol, new_symbol); } @@ -523,9 +525,9 @@ impl<'db> UseDefMapBuilder<'db> { state.record_visibility_constraint(&mut self.visibility_constraints, new_constraint_id); } - self.unbound_visibility_constraint_id = self + self.unbound_visibility = self .visibility_constraints - .add_sequence(self.unbound_visibility_constraint_id, new_constraint_id); + .add_sequence(self.unbound_visibility, new_constraint_id); new_constraint_id } @@ -534,7 +536,7 @@ impl<'db> UseDefMapBuilder<'db> { let num_symbols = self.symbol_states.len(); debug_assert!(num_symbols >= snapshot.symbol_states.len()); - self.unbound_visibility_constraint_id = snapshot.unbound_visibility_constraint_id; + self.unbound_visibility = snapshot.unbound_visibility; let mut snapshot_definitions_iter = snapshot.symbol_states.into_iter(); for current in &mut self.symbol_states { @@ -585,7 +587,7 @@ impl<'db> UseDefMapBuilder<'db> { pub(super) fn snapshot(&self) -> FlowSnapshot { FlowSnapshot { symbol_states: self.symbol_states.clone(), - unbound_visibility_constraint_id: self.unbound_visibility_constraint_id, + unbound_visibility: self.unbound_visibility, } } @@ -599,15 +601,13 @@ impl<'db> UseDefMapBuilder<'db> { // Restore the current visible-definitions state to the given snapshot. self.symbol_states = snapshot.symbol_states; - self.unbound_visibility_constraint_id = snapshot.unbound_visibility_constraint_id; + self.unbound_visibility = snapshot.unbound_visibility; // 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 // snapshot, the correct state to fill them in with is "undefined". - self.symbol_states.resize( - num_symbols, - SymbolState::undefined(self.unbound_visibility_constraint_id), - ); + self.symbol_states + .resize(num_symbols, SymbolState::undefined(self.unbound_visibility)); } /// Merge the given snapshot into the current state, reflecting that we might have taken either @@ -625,19 +625,16 @@ impl<'db> UseDefMapBuilder<'db> { current.merge(snapshot, &mut self.visibility_constraints); } else { current.merge( - SymbolState::undefined(snapshot.unbound_visibility_constraint_id), + SymbolState::undefined(snapshot.unbound_visibility), &mut self.visibility_constraints, ); // Symbol not present in snapshot, so it's unbound/undeclared from that path. } } - // Merge unbound visibility constraints: - - self.unbound_visibility_constraint_id = self.visibility_constraints.add_merged( - self.unbound_visibility_constraint_id, - snapshot.unbound_visibility_constraint_id, - ); + self.unbound_visibility = self + .visibility_constraints + .add_merged(self.unbound_visibility, snapshot.unbound_visibility); } pub(super) fn finish(mut self) -> UseDefMap<'db> { diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs index 67b49d10ea351..73c76b06f9c70 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs @@ -110,11 +110,11 @@ pub(super) struct SymbolDeclarations { } impl SymbolDeclarations { - fn undeclared(unbound_visibility_constraint_id: ScopedVisibilityConstraintId) -> Self { + fn undeclared(undeclared_visibility: ScopedVisibilityConstraintId) -> Self { Self { live_declarations: Declarations::with(0), visibility_constraints: VisibilityConstraintPerBinding::from_iter([ - unbound_visibility_constraint_id, + undeclared_visibility, ]), } } @@ -173,13 +173,11 @@ pub(super) struct SymbolBindings { } impl SymbolBindings { - fn unbound(unbound_visibility_constraint_id: ScopedVisibilityConstraintId) -> Self { + fn unbound(unbound_visibility: ScopedVisibilityConstraintId) -> Self { Self { live_bindings: Bindings::with(0), constraints: ConstraintsPerBinding::from_iter([Constraints::default()]), - visibility_constraints: VisibilityConstraintPerBinding::from_iter([ - unbound_visibility_constraint_id, - ]), + visibility_constraints: VisibilityConstraintPerBinding::from_iter([unbound_visibility]), } } @@ -238,10 +236,10 @@ pub(super) struct SymbolState { impl SymbolState { /// Return a new [`SymbolState`] representing an unbound, undeclared symbol. - pub(super) fn undefined(unbound_visibility_constraint: ScopedVisibilityConstraintId) -> Self { + pub(super) fn undefined(unbound_visibility: ScopedVisibilityConstraintId) -> Self { Self { - declarations: SymbolDeclarations::undeclared(unbound_visibility_constraint), - bindings: SymbolBindings::unbound(unbound_visibility_constraint), + declarations: SymbolDeclarations::undeclared(unbound_visibility), + bindings: SymbolBindings::unbound(unbound_visibility), } } From 8a93a9a55a9d66b20baf2b11f520628c4b355ad8 Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 18 Dec 2024 09:48:04 +0100 Subject: [PATCH 23/65] Refactor --- .../src/semantic_index.rs | 2 +- .../src/semantic_index/use_def.rs | 16 +++++---- .../semantic_index/use_def/symbol_state.rs | 36 ++++++++++++------- .../src/visibility_constraints.rs | 11 +++--- 4 files changed, 39 insertions(+), 26 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index.rs b/crates/red_knot_python_semantic/src/semantic_index.rs index 5633de4b751d2..b597717bb9254 100644 --- a/crates/red_knot_python_semantic/src/semantic_index.rs +++ b/crates/red_knot_python_semantic/src/semantic_index.rs @@ -29,7 +29,7 @@ pub mod symbol; mod use_def; pub(crate) use self::use_def::{ - BindingWithConstraints, BindingWithConstraintsIterator, DeclarationsIterator, + AllConstraints, BindingWithConstraints, BindingWithConstraintsIterator, DeclarationsIterator, ScopedConstraintId, ScopedVisibilityConstraintId, }; diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index d0566605321fe..b89cbbc07bab7 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -238,6 +238,8 @@ use super::constraint::Constraint; mod bitset; mod symbol_state; +pub(crate) type AllConstraints<'db> = IndexVec>; + /// Applicable definitions and constraints for every use of a name. #[derive(Debug, PartialEq, Eq)] pub(crate) struct UseDefMap<'db> { @@ -245,7 +247,7 @@ pub(crate) struct UseDefMap<'db> { all_definitions: IndexVec>>, /// Array of [`Constraint`] in this scope. - all_constraints: IndexVec>, + all_constraints: AllConstraints<'db>, /// Array of [`VisibilityConstraintRef`] in this scope. visibility_constraints: VisibilityConstraints, @@ -358,7 +360,7 @@ enum SymbolDefinitions { #[derive(Debug)] pub(crate) struct BindingWithConstraintsIterator<'map, 'db> { all_definitions: &'map IndexVec>>, - all_constraints: &'map IndexVec>, + all_constraints: &'map AllConstraints<'db>, inner: BindingIdWithConstraintsIterator<'map, 'db>, } @@ -388,13 +390,13 @@ impl std::iter::FusedIterator for BindingWithConstraintsIterator<'_, '_> {} pub(crate) struct BindingWithConstraints<'map, 'db> { pub(crate) binding: Option>, pub(crate) constraints: ConstraintsIterator<'map, 'db>, - pub(crate) all_constraints: &'map IndexVec>, + pub(crate) all_constraints: &'map AllConstraints<'db>, pub(crate) visibility_constraints: &'map VisibilityConstraints, pub(crate) visibility_constraint: ScopedVisibilityConstraintId, } pub(crate) struct ConstraintsIterator<'map, 'db> { - all_constraints: &'map IndexVec>, + all_constraints: &'map AllConstraints<'db>, constraint_ids: ConstraintIdIterator<'map>, } @@ -418,7 +420,7 @@ pub(crate) struct DeclarationsIterator<'map, 'db> { impl<'map, 'db> Iterator for DeclarationsIterator<'map, 'db> { type Item = ( Option>, - &'map IndexVec>, + &'map AllConstraints<'db>, &'map VisibilityConstraints, ScopedVisibilityConstraintId, ); @@ -452,7 +454,7 @@ pub(super) struct UseDefMapBuilder<'db> { all_definitions: IndexVec>>, /// Append-only array of [`Constraint`]. - all_constraints: IndexVec>, + all_constraints: AllConstraints<'db>, /// Append-only array of [`VisibilityConstraintRef`]. visibility_constraints: VisibilityConstraints, @@ -477,7 +479,7 @@ impl<'db> UseDefMapBuilder<'db> { all_definitions: IndexVec::from_iter([None]), all_constraints: IndexVec::new(), visibility_constraints: VisibilityConstraints::new(), - unbound_visibility: ScopedVisibilityConstraintId::from_u32(0), + unbound_visibility: ScopedVisibilityConstraintId::ALWAYS_VISIBLE, bindings_by_use: IndexVec::new(), definitions_by_definition: FxHashMap::default(), symbol_states: IndexVec::new(), diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs index 73c76b06f9c70..f459063036ae8 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs @@ -43,10 +43,10 @@ //! //! Tracking live declarations is simpler, since constraints are not involved, but otherwise very //! similar to tracking live bindings. -use crate::semantic_index::use_def::{Constraint, VisibilityConstraints}; +use crate::semantic_index::{use_def::VisibilityConstraints, AllConstraints}; use super::bitset::{BitSet, BitSetIterator}; -use ruff_index::{newtype_index, IndexVec}; +use ruff_index::newtype_index; use smallvec::SmallVec; /// A newtype-index for a definition in a particular scope. @@ -92,6 +92,12 @@ type ConstraintsIntoIterator = smallvec::IntoIter; /// Similar to what we have above, but for visibility constraints. #[newtype_index] pub(crate) struct ScopedVisibilityConstraintId; + +impl ScopedVisibilityConstraintId { + pub(crate) const ALWAYS_VISIBLE: ScopedVisibilityConstraintId = + ScopedVisibilityConstraintId::from_u32(0); +} + const INLINE_VISIBILITY_CONSTRAINTS: usize = 4; type InlineVisibilityConstraintsArray = [ScopedVisibilityConstraintId; INLINE_VISIBILITY_CONSTRAINTS]; @@ -125,7 +131,7 @@ impl SymbolDeclarations { self.visibility_constraints = VisibilityConstraintPerBinding::with_capacity(1); self.visibility_constraints - .push(ScopedVisibilityConstraintId::from_u32(0)); + .push(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); } /// Add given visibility constraint to all live bindings. @@ -142,7 +148,7 @@ impl SymbolDeclarations { // /// Return an iterator over live declarations for this symbol. // pub(super) fn iter<'map, 'db>( // &'db self, - // all_constraints: &'map IndexVec>, + // all_constraints: &'map AllConstraints<'db>, // ) -> DeclarationIdIterator<'map, 'db> { // DeclarationIdIterator { // all_constraints, @@ -191,7 +197,7 @@ impl SymbolBindings { self.visibility_constraints = VisibilityConstraintPerBinding::with_capacity(1); self.visibility_constraints - .push(ScopedVisibilityConstraintId::from_u32(0)); + .push(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); } /// Add given constraint to all live bindings. @@ -215,7 +221,7 @@ impl SymbolBindings { /// Iterate over currently live bindings for this symbol pub(super) fn iter<'map, 'db>( &'map self, - all_constraints: &'map IndexVec>, + all_constraints: &'map AllConstraints<'db>, visibility_constraints: &'map VisibilityConstraints, ) -> BindingIdWithConstraintsIterator<'map, 'db> { BindingIdWithConstraintsIterator { @@ -495,14 +501,14 @@ impl SymbolState { pub(super) struct BindingIdWithConstraints<'map, 'db> { pub(super) definition: ScopedDefinitionId, pub(super) constraint_ids: ConstraintIdIterator<'map>, - pub(super) all_constraints: &'map IndexVec>, + pub(super) all_constraints: &'map AllConstraints<'db>, pub(super) visibility_constraints: &'map VisibilityConstraints, pub(super) visibility_constraint: ScopedVisibilityConstraintId, } #[derive(Debug)] pub(super) struct BindingIdWithConstraintsIterator<'map, 'db> { - all_constraints: &'map IndexVec>, + all_constraints: &'map AllConstraints<'db>, visibility_constraints: &'map VisibilityConstraints, definitions: BindingsIterator<'map>, constraints: ConstraintsIterator<'map>, @@ -554,7 +560,7 @@ impl Iterator for ConstraintIdIterator<'_> { impl std::iter::FusedIterator for ConstraintIdIterator<'_> {} pub(super) struct DeclarationIdIterator<'map, 'db> { - pub(crate) all_constraints: &'map IndexVec>, + pub(crate) all_constraints: &'map AllConstraints<'db>, pub(crate) visibility_constraints: &'map VisibilityConstraints, pub(crate) declarations_iter: DeclarationsIterator<'map>, pub(crate) visibility_constraints_iter: VisibilityConstraintsIterator<'map>, @@ -563,7 +569,7 @@ pub(super) struct DeclarationIdIterator<'map, 'db> { impl<'map, 'db> Iterator for DeclarationIdIterator<'map, 'db> { type Item = ( ScopedDefinitionId, - &'map IndexVec>, + &'map AllConstraints<'db>, &'map VisibilityConstraints, ScopedVisibilityConstraintId, ); @@ -590,11 +596,13 @@ impl std::iter::FusedIterator for DeclarationIdIterator<'_, '_> {} #[cfg(test)] mod tests { + // use crate::visibility_constraints; + // use super::*; // #[track_caller] // fn assert_bindings(symbol: &SymbolState, may_be_unbound: bool, expected: &[&str]) { - // assert_eq!(symbol.bindings.may_be_unbound, may_be_unbound); + // // assert_eq!(symbol.bindings.may_be_unbound, may_be_unbound); // let mut actual = symbol // .bindings() // .iter() @@ -621,7 +629,7 @@ mod tests { // may_be_undeclared: bool, // expected: &[u32], // ) { - // assert_eq!(symbol.declarations.may_be_undeclared(), may_be_undeclared); + // // assert_eq!(symbol.declarations.may_be_undeclared(), may_be_undeclared); // let mut actual = symbol // .declarations() // .iter() @@ -633,7 +641,9 @@ mod tests { // #[test] // fn unbound() { - // let sym = SymbolState::undefined(); + // let visibility_constraints = VisibilityConstraints::new(); + // let unbound_visibility = ScopedVisibilityConstraintId::ALWAYS_VISIBLE; + // let sym = SymbolState::undefined(unbound_visibility); // assert_bindings(&sym, true, &[]); // } diff --git a/crates/red_knot_python_semantic/src/visibility_constraints.rs b/crates/red_knot_python_semantic/src/visibility_constraints.rs index 24efa53b5d9f3..157e746333d36 100644 --- a/crates/red_knot_python_semantic/src/visibility_constraints.rs +++ b/crates/red_knot_python_semantic/src/visibility_constraints.rs @@ -3,6 +3,7 @@ use ruff_index::IndexVec; use crate::semantic_index::{ ast_ids::HasScopedExpressionId, constraint::{Constraint, ConstraintNode, PatternConstraintKind}, + AllConstraints, }; use crate::semantic_index::{ScopedConstraintId, ScopedVisibilityConstraintId}; use crate::types::{infer_expression_types, Truthiness}; @@ -42,10 +43,10 @@ impl VisibilityConstraints { ) -> ScopedVisibilityConstraintId { match (&self.constraints[a], &self.constraints[b]) { (_, VisibilityConstraint::VisibleIfNot(id)) if a == *id => { - ScopedVisibilityConstraintId::from_u32(0) + ScopedVisibilityConstraintId::ALWAYS_VISIBLE } (VisibilityConstraint::VisibleIfNot(id), _) if *id == b => { - ScopedVisibilityConstraintId::from_u32(0) + ScopedVisibilityConstraintId::ALWAYS_VISIBLE } _ => self.add(VisibilityConstraint::Merged(a, b)), } @@ -56,7 +57,7 @@ impl VisibilityConstraints { a: ScopedVisibilityConstraintId, b: ScopedVisibilityConstraintId, ) -> ScopedVisibilityConstraintId { - if a == ScopedVisibilityConstraintId::from_u32(0) { + if a == ScopedVisibilityConstraintId::ALWAYS_VISIBLE { b } else { self.add(VisibilityConstraint::Sequence(a, b)) @@ -67,7 +68,7 @@ impl VisibilityConstraints { pub(crate) fn analyze<'db>( self: &VisibilityConstraints, db: &'db dyn Db, - all_constraints: &IndexVec>, + all_constraints: &AllConstraints<'db>, id: ScopedVisibilityConstraintId, ) -> Truthiness { self.analyze_impl(db, all_constraints, id, MAX_RECURSION_DEPTH) @@ -76,7 +77,7 @@ impl VisibilityConstraints { fn analyze_impl<'db>( self: &VisibilityConstraints, db: &'db dyn Db, - constraints: &IndexVec>, + constraints: &AllConstraints<'db>, id: ScopedVisibilityConstraintId, max_depth: usize, ) -> Truthiness { From 12f139df8782db1790bda3945d590b3a2665e94e Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 18 Dec 2024 10:23:47 +0100 Subject: [PATCH 24/65] Reactivate symbol_state tests --- .../src/semantic_index/use_def.rs | 9 +- .../semantic_index/use_def/symbol_state.rs | 368 ++++++++++-------- 2 files changed, 200 insertions(+), 177 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index b89cbbc07bab7..bd5c24c5c5c13 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -338,14 +338,7 @@ impl<'db> UseDefMap<'db> { ) -> DeclarationsIterator<'map, 'db> { DeclarationsIterator { all_definitions: &self.all_definitions, - inner: { - DeclarationIdIterator { - all_constraints: &self.all_constraints, - visibility_constraints: &self.visibility_constraints, - declarations_iter: declarations.live_declarations.iter(), - visibility_constraints_iter: declarations.visibility_constraints.iter(), - } - }, + inner: declarations.iter(&self.all_constraints, &self.visibility_constraints), } } } diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs index f459063036ae8..52770856b060c 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs @@ -53,6 +53,10 @@ use smallvec::SmallVec; #[newtype_index] pub(super) struct ScopedDefinitionId; +impl ScopedDefinitionId { + pub(super) const UNBOUND: ScopedDefinitionId = ScopedDefinitionId::from_u32(0); +} + /// A newtype-index for a constraint expression in a particular scope. #[newtype_index] pub(crate) struct ScopedConstraintId; @@ -145,17 +149,19 @@ impl SymbolDeclarations { } } - // /// Return an iterator over live declarations for this symbol. - // pub(super) fn iter<'map, 'db>( - // &'db self, - // all_constraints: &'map AllConstraints<'db>, - // ) -> DeclarationIdIterator<'map, 'db> { - // DeclarationIdIterator { - // all_constraints, - // inner: self.live_declarations.iter(), - // visibility_constraints: self.visibility_constraints.iter(), - // } - // } + /// Return an iterator over live declarations for this symbol. + pub(super) fn iter<'map, 'db>( + &'map self, + all_constraints: &'map AllConstraints<'db>, + visibility_constraints: &'map VisibilityConstraints, + ) -> DeclarationIdIterator<'map, 'db> { + DeclarationIdIterator { + all_constraints, + visibility_constraints, + declarations_iter: self.live_declarations.iter(), + visibility_constraints_iter: self.visibility_constraints.iter(), + } + } // pub(super) fn is_empty(&self) -> bool { // self.live_declarations.is_empty() @@ -251,6 +257,7 @@ impl SymbolState { /// Record a newly-encountered binding for this symbol. pub(super) fn record_binding(&mut self, binding_id: ScopedDefinitionId) { + debug_assert!(binding_id != ScopedDefinitionId::UNBOUND); self.bindings.record_binding(binding_id); } @@ -596,184 +603,207 @@ impl std::iter::FusedIterator for DeclarationIdIterator<'_, '_> {} #[cfg(test)] mod tests { - // use crate::visibility_constraints; - - // use super::*; - - // #[track_caller] - // fn assert_bindings(symbol: &SymbolState, may_be_unbound: bool, expected: &[&str]) { - // // assert_eq!(symbol.bindings.may_be_unbound, may_be_unbound); - // let mut actual = symbol - // .bindings() - // .iter() - // .map(|def_id_with_constraints| { - // format!( - // "{}<{}>", - // def_id_with_constraints.definition.as_u32(), - // def_id_with_constraints - // .constraint_ids - // .map(ScopedConstraintId::as_u32) - // .map(|idx| idx.to_string()) - // .collect::>() - // .join(", ") - // ) - // }) - // .collect::>(); - // actual.reverse(); - // assert_eq!(actual, expected); - // } - - // #[track_caller] - // pub(crate) fn assert_declarations( - // symbol: &SymbolState, - // may_be_undeclared: bool, - // expected: &[u32], - // ) { - // // assert_eq!(symbol.declarations.may_be_undeclared(), may_be_undeclared); - // let mut actual = symbol - // .declarations() - // .iter() - // .map(|(d, _)| d.as_u32()) - // .collect::>(); - // actual.reverse(); - // assert_eq!(actual, expected); - // } - - // #[test] - // fn unbound() { - // let visibility_constraints = VisibilityConstraints::new(); - // let unbound_visibility = ScopedVisibilityConstraintId::ALWAYS_VISIBLE; - // let sym = SymbolState::undefined(unbound_visibility); - - // assert_bindings(&sym, true, &[]); - // } - - // #[test] - // fn with() { - // let mut sym = SymbolState::undefined(); - // sym.record_binding(ScopedDefinitionId::from_u32(0)); + use super::*; + + #[track_caller] + fn assert_bindings<'db>( + constraints: &AllConstraints<'db>, + visibility_constraints: &VisibilityConstraints, + symbol: &SymbolState, + expected: &[&str], + ) { + let actual = symbol + .bindings() + .iter(&constraints, &visibility_constraints) + .map(|def_id_with_constraints| { + let def_id = def_id_with_constraints.definition; + let def = if def_id == ScopedDefinitionId::UNBOUND { + "unbound".into() + } else { + def_id.as_u32().to_string() + }; + let constraints = def_id_with_constraints + .constraint_ids + .map(ScopedConstraintId::as_u32) + .map(|idx| idx.to_string()) + .collect::>() + .join(", "); + format!("{def}<{constraints}>") + }) + .collect::>(); + assert_eq!(actual, expected); + } - // assert_bindings(&sym, false, &["0<>"]); - // } + #[track_caller] + pub(crate) fn assert_declarations<'db>( + constraints: &AllConstraints<'db>, + visibility_constraints: &VisibilityConstraints, + symbol: &SymbolState, + expected: &[&str], + ) { + let actual = symbol + .declarations() + .iter(&constraints, &visibility_constraints) + .map(|(def_id, _, _, _)| { + if def_id == ScopedDefinitionId::UNBOUND { + "undeclared".into() + } else { + def_id.as_u32().to_string() + } + }) + .collect::>(); + assert_eq!(actual, expected); + } - // #[test] - // fn set_may_be_unbound() { - // let mut sym = SymbolState::undefined(); - // sym.record_binding(ScopedDefinitionId::from_u32(0)); - // sym.set_may_be_unbound(); + #[test] + fn unbound() { + let constraints = AllConstraints::new(); + let visibility_constraints = VisibilityConstraints::new(); + let sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); - // assert_bindings(&sym, true, &["0<>"]); - // } + assert_bindings(&constraints, &visibility_constraints, &sym, &["unbound<>"]); + } - // #[test] - // fn record_constraint() { - // let mut sym = SymbolState::undefined(); - // sym.record_binding(ScopedDefinitionId::from_u32(0)); - // sym.record_constraint(ScopedConstraintId::from_u32(0)); + #[test] + fn with() { + let constraints = AllConstraints::new(); + let visibility_constraints = VisibilityConstraints::new(); + let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + sym.record_binding(ScopedDefinitionId::from_u32(1)); - // assert_bindings(&sym, false, &["0<0>"]); - // } + assert_bindings(&constraints, &visibility_constraints, &sym, &["1<>"]); + } - // #[test] - // fn merge() { - // // merging the same definition with the same constraint keeps the constraint - // let mut sym0a = SymbolState::undefined(); - // sym0a.record_binding(ScopedDefinitionId::from_u32(0)); - // sym0a.record_constraint(ScopedConstraintId::from_u32(0)); - - // let mut sym0b = SymbolState::undefined(); - // sym0b.record_binding(ScopedDefinitionId::from_u32(0)); - // sym0b.record_constraint(ScopedConstraintId::from_u32(0)); - - // sym0a.merge(sym0b); - // let mut sym0 = sym0a; - // assert_bindings(&sym0, false, &["0<0>"]); - - // // merging the same definition with differing constraints drops all constraints - // let mut sym1a = SymbolState::undefined(); - // sym1a.record_binding(ScopedDefinitionId::from_u32(1)); - // sym1a.record_constraint(ScopedConstraintId::from_u32(1)); - - // let mut sym1b = SymbolState::undefined(); - // sym1b.record_binding(ScopedDefinitionId::from_u32(1)); - // sym1b.record_constraint(ScopedConstraintId::from_u32(2)); - - // sym1a.merge(sym1b); - // let sym1 = sym1a; - // assert_bindings(&sym1, false, &["1<>"]); - - // // merging a constrained definition with unbound keeps both - // let mut sym2a = SymbolState::undefined(); - // sym2a.record_binding(ScopedDefinitionId::from_u32(2)); - // sym2a.record_constraint(ScopedConstraintId::from_u32(3)); - - // let sym2b = SymbolState::undefined(); - - // sym2a.merge(sym2b); - // let sym2 = sym2a; - // assert_bindings(&sym2, true, &["2<3>"]); - - // // merging different definitions keeps them each with their existing constraints - // sym0.merge(sym2); - // let sym = sym0; - // assert_bindings(&sym, true, &["0<0>", "2<3>"]); - // } + #[test] + fn record_constraint() { + let constraints = AllConstraints::new(); + let visibility_constraints = VisibilityConstraints::new(); + let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + sym.record_binding(ScopedDefinitionId::from_u32(1)); + sym.record_constraint(ScopedConstraintId::from_u32(0)); - // #[test] - // fn no_declaration() { - // let sym = SymbolState::undefined(); + assert_bindings(&constraints, &visibility_constraints, &sym, &["1<0>"]); + } - // assert_declarations(&sym, true, &[]); - // } + #[test] + fn merge() { + let constraints = AllConstraints::new(); + let mut visibility_constraints = VisibilityConstraints::new(); + + // merging the same definition with the same constraint keeps the constraint + let mut sym1a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + sym1a.record_binding(ScopedDefinitionId::from_u32(1)); + sym1a.record_constraint(ScopedConstraintId::from_u32(0)); + + let mut sym1b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + sym1b.record_binding(ScopedDefinitionId::from_u32(1)); + sym1b.record_constraint(ScopedConstraintId::from_u32(0)); + + sym1a.merge(sym1b, &mut visibility_constraints); + let mut sym1 = sym1a; + assert_bindings(&constraints, &visibility_constraints, &sym1, &["1<0>"]); + + // merging the same definition with differing constraints drops all constraints + let mut sym2a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + sym2a.record_binding(ScopedDefinitionId::from_u32(2)); + sym2a.record_constraint(ScopedConstraintId::from_u32(1)); + + let mut sym1b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + sym1b.record_binding(ScopedDefinitionId::from_u32(2)); + sym1b.record_constraint(ScopedConstraintId::from_u32(2)); + + sym2a.merge(sym1b, &mut visibility_constraints); + let sym2 = sym2a; + assert_bindings(&constraints, &visibility_constraints, &sym2, &["2<>"]); + + // merging a constrained definition with unbound keeps both + let mut sym3a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + sym3a.record_binding(ScopedDefinitionId::from_u32(3)); + sym3a.record_constraint(ScopedConstraintId::from_u32(3)); + + let sym2b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + + sym3a.merge(sym2b, &mut visibility_constraints); + let sym3 = sym3a; + assert_bindings( + &constraints, + &visibility_constraints, + &sym3, + &["unbound<>", "3<3>"], + ); + + // merging different definitions keeps them each with their existing constraints + sym1.merge(sym3, &mut visibility_constraints); + let sym = sym1; + assert_bindings( + &constraints, + &visibility_constraints, + &sym, + &["unbound<>", "1<0>", "3<3>"], + ); + } - // #[test] - // fn record_declaration() { - // let mut sym = SymbolState::undefined(); - // sym.record_declaration(ScopedDefinitionId::from_u32(1)); + #[test] + fn no_declaration() { + let constraints = AllConstraints::new(); + let visibility_constraints = VisibilityConstraints::new(); + let sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); - // assert_declarations(&sym, false, &[1]); - // } + assert_declarations(&constraints, &visibility_constraints, &sym, &["undeclared"]); + } - // #[test] - // fn record_declaration_override() { - // let mut sym = SymbolState::undefined(); - // sym.record_declaration(ScopedDefinitionId::from_u32(1)); - // sym.record_declaration(ScopedDefinitionId::from_u32(2)); + #[test] + fn record_declaration() { + let constraints = AllConstraints::new(); + let visibility_constraints = VisibilityConstraints::new(); + let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + sym.record_declaration(ScopedDefinitionId::from_u32(1)); - // assert_declarations(&sym, false, &[2]); - // } + assert_declarations(&constraints, &visibility_constraints, &sym, &["1"]); + } - // #[test] - // fn record_declaration_merge() { - // let mut sym = SymbolState::undefined(); - // sym.record_declaration(ScopedDefinitionId::from_u32(1)); + #[test] + fn record_declaration_override() { + let constraints = AllConstraints::new(); + let visibility_constraints = VisibilityConstraints::new(); + let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + sym.record_declaration(ScopedDefinitionId::from_u32(1)); + sym.record_declaration(ScopedDefinitionId::from_u32(2)); - // let mut sym2 = SymbolState::undefined(); - // sym2.record_declaration(ScopedDefinitionId::from_u32(2)); + assert_declarations(&constraints, &visibility_constraints, &sym, &["2"]); + } - // sym.merge(sym2); + #[test] + fn record_declaration_merge() { + let constraints = AllConstraints::new(); + let mut visibility_constraints = VisibilityConstraints::new(); + let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + sym.record_declaration(ScopedDefinitionId::from_u32(1)); - // assert_declarations(&sym, false, &[1, 2]); - // } + let mut sym2 = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + sym2.record_declaration(ScopedDefinitionId::from_u32(2)); - // #[test] - // fn record_declaration_merge_partial_undeclared() { - // let mut sym = SymbolState::undefined(); - // sym.record_declaration(ScopedDefinitionId::from_u32(1)); + sym.merge(sym2, &mut visibility_constraints); - // let sym2 = SymbolState::undefined(); + assert_declarations(&constraints, &visibility_constraints, &sym, &["1", "2"]); + } - // sym.merge(sym2); + #[test] + fn record_declaration_merge_partial_undeclared() { + let constraints = AllConstraints::new(); + let mut visibility_constraints = VisibilityConstraints::new(); + let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + sym.record_declaration(ScopedDefinitionId::from_u32(1)); - // assert_declarations(&sym, true, &[1]); - // } + let sym2 = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); - // #[test] - // fn set_may_be_undeclared() { - // let mut sym = SymbolState::undefined(); - // sym.record_declaration(ScopedDefinitionId::from_u32(0)); - // sym.set_may_be_undeclared(); + sym.merge(sym2, &mut visibility_constraints); - // assert_declarations(&sym, true, &[0]); - // } + assert_declarations( + &constraints, + &visibility_constraints, + &sym, + &["undeclared", "1"], + ); + } } From 9a1d1ea33bc215f783ef8d459840b7a422018ff4 Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 18 Dec 2024 10:31:08 +0100 Subject: [PATCH 25/65] sys.platform documentation --- crates/red_knot_python_semantic/src/python_platform.rs | 8 ++++++-- crates/red_knot_python_semantic/src/types.rs | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/red_knot_python_semantic/src/python_platform.rs b/crates/red_knot_python_semantic/src/python_platform.rs index d6affbfcebc38..672db29459b16 100644 --- a/crates/red_knot_python_semantic/src/python_platform.rs +++ b/crates/red_knot_python_semantic/src/python_platform.rs @@ -9,7 +9,11 @@ pub enum PythonPlatform { /// Do not make any assumptions about the target platform. #[default] All, - /// Assume a target platform like `linux`, `darwin`, `win32`, etc. + /// Assume a specific target platform like `linux`, `darwin` or `win32`. + /// + /// We use a string (instead of individual enum variants), as the set of possible platforms + /// may change over time. See for + /// some known platform identifiers. #[cfg_attr(feature = "serde", serde(untagged))] - Individual(String), + Identifier(String), } diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 5e0fd81b8f8ac..19c4d4c10d0da 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -139,7 +139,7 @@ fn symbol<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str) -> Symbol<'db> && file_to_module(db, scope.file(db)).is_some_and(|module| module.name() == "sys") { match Program::get(db).python_platform(db) { - crate::PythonPlatform::Individual(platform) => { + crate::PythonPlatform::Identifier(platform) => { return Symbol::Type( Type::StringLiteral(StringLiteralType::new(db, platform.as_str())), Boundness::Bound, From 81bfc5227b1deb39d6b13751010d0f8398db6dc4 Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 18 Dec 2024 10:47:09 +0100 Subject: [PATCH 26/65] Clippy --- .../src/semantic_index/constraint.rs | 2 +- .../src/semantic_index/use_def/symbol_state.rs | 16 ++++++---------- crates/red_knot_python_semantic/src/types.rs | 4 ++-- .../red_knot_python_semantic/src/types/infer.rs | 5 ----- 4 files changed, 9 insertions(+), 18 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index/constraint.rs b/crates/red_knot_python_semantic/src/semantic_index/constraint.rs index 05aa225d063d5..3e7a8fc66856c 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/constraint.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/constraint.rs @@ -17,7 +17,7 @@ pub(crate) enum ConstraintNode<'db> { Pattern(PatternConstraint<'db>), } -/// Pattern kinds for which we do support type narrowing and/or static truthiness analysis. +/// Pattern kinds for which we support type narrowing and/or static visibility analysis. #[derive(Debug, Clone, PartialEq)] pub(crate) enum PatternConstraintKind<'db> { Singleton(Singleton, Option>), diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs index 52770856b060c..e0f2c224ba520 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs @@ -162,10 +162,6 @@ impl SymbolDeclarations { visibility_constraints_iter: self.visibility_constraints.iter(), } } - - // pub(super) fn is_empty(&self) -> bool { - // self.live_declarations.is_empty() - // } } /// Live bindings and narrowing constraints for a single symbol at some point in control flow. @@ -606,15 +602,15 @@ mod tests { use super::*; #[track_caller] - fn assert_bindings<'db>( - constraints: &AllConstraints<'db>, + fn assert_bindings( + constraints: &AllConstraints<'_>, visibility_constraints: &VisibilityConstraints, symbol: &SymbolState, expected: &[&str], ) { let actual = symbol .bindings() - .iter(&constraints, &visibility_constraints) + .iter(constraints, visibility_constraints) .map(|def_id_with_constraints| { let def_id = def_id_with_constraints.definition; let def = if def_id == ScopedDefinitionId::UNBOUND { @@ -635,15 +631,15 @@ mod tests { } #[track_caller] - pub(crate) fn assert_declarations<'db>( - constraints: &AllConstraints<'db>, + pub(crate) fn assert_declarations( + constraints: &AllConstraints<'_>, visibility_constraints: &VisibilityConstraints, symbol: &SymbolState, expected: &[&str], ) { let actual = symbol .declarations() - .iter(&constraints, &visibility_constraints) + .iter(constraints, visibility_constraints) .map(|(def_id, _, _, _)| { if def_id == ScopedDefinitionId::UNBOUND { "undeclared".into() diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 19c4d4c10d0da..6b289903d2073 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -69,6 +69,8 @@ pub fn check_types(db: &dyn Db, file: File) -> TypeCheckDiagnostics { /// Infer the public type of a symbol (its type as seen from outside its scope). fn symbol_by_id<'db>(db: &'db dyn Db, scope: ScopeId<'db>, symbol: ScopedSymbolId) -> Symbol<'db> { + let _span = tracing::trace_span!("symbol_by_id", ?symbol).entered(); + let use_def = use_def_map(db, scope); // If the symbol is declared, the public type is based on declarations; otherwise, it's based @@ -125,8 +127,6 @@ fn symbol_by_id<'db>(db: &'db dyn Db, scope: ScopeId<'db>, symbol: ScopedSymbolI /// Shorthand for `symbol_by_id` that takes a symbol name instead of an ID. fn symbol<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str) -> Symbol<'db> { - let _span = tracing::trace_span!("symbol", ?name).entered(); - // We don't need to check for `typing_extensions` here, because `typing_extensions.TYPE_CHECKING` // is just a re-export of `typing.TYPE_CHECKING`. if name == "TYPE_CHECKING" diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 851d39fadd73f..64b16a0ad641d 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -824,11 +824,6 @@ impl<'db> TypeInferenceBuilder<'db> { debug_assert!(binding.is_binding(self.db())); let use_def = self.index.use_def_map(binding.file_scope(self.db())); let declarations = use_def.declarations_at_binding(binding); - // let undeclared_ty = if declarations.clone().may_be_undeclared(self.db) { - // Symbol::Type(Type::Unknown, Boundness::Bound) - // } else { - // Symbol::Unbound - // }; let mut bound_ty = ty; let declared_ty = declarations_ty(self.db(), declarations) .map(|s| s.ignore_possibly_unbound().unwrap_or(Type::Unknown)) From 189f2ef3ccefc9e173deec0c33e212b8c3442fd2 Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 18 Dec 2024 11:03:25 +0100 Subject: [PATCH 27/65] Fix fuzz build --- fuzz/fuzz_targets/red_knot_check_invalid_syntax.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fuzz/fuzz_targets/red_knot_check_invalid_syntax.rs b/fuzz/fuzz_targets/red_knot_check_invalid_syntax.rs index 1259b8c33687d..f547a7c4760c8 100644 --- a/fuzz/fuzz_targets/red_knot_check_invalid_syntax.rs +++ b/fuzz/fuzz_targets/red_knot_check_invalid_syntax.rs @@ -10,7 +10,7 @@ use libfuzzer_sys::{fuzz_target, Corpus}; use red_knot_python_semantic::types::check_types; use red_knot_python_semantic::{ default_lint_registry, lint::RuleSelection, Db as SemanticDb, Program, ProgramSettings, - PythonVersion, SearchPathSettings, + PythonPlatform, PythonVersion, SearchPathSettings, }; use ruff_db::files::{system_path_to_file, File, Files}; use ruff_db::system::{DbWithTestSystem, System, SystemPathBuf, TestSystem}; @@ -112,6 +112,7 @@ fn setup_db() -> TestDb { &db, &ProgramSettings { python_version: PythonVersion::default(), + python_platform: PythonPlatform::default(), search_paths: SearchPathSettings::new(src_root), }, ) From c1748f07d6e8130ce21e31351ae60d7de5308f04 Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 18 Dec 2024 11:05:11 +0100 Subject: [PATCH 28/65] Extend sys.platform tests --- .../resources/mdtest/sys_platform.md | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/crates/red_knot_python_semantic/resources/mdtest/sys_platform.md b/crates/red_knot_python_semantic/resources/mdtest/sys_platform.md index 40cf1e2b6710c..0c5d6944c9a8e 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/sys_platform.md +++ b/crates/red_knot_python_semantic/resources/mdtest/sys_platform.md @@ -41,3 +41,31 @@ import sys reveal_type(sys.platform) # revealed: Literal["linux"] ``` + +## Testing for a specific platform + +### Exact comparison + +```toml +[environment] +python-platform = "freebsd8" +``` + +```py +import sys + +reveal_type(sys.platform == "freebsd8") # revealed: Literal[True] +reveal_type(sys.platform == "linux") # revealed: Literal[False] +``` + +### Substring comparison + +It is [recommended](https://docs.python.org/3/library/sys.html#sys.platform) to use +`sys.platform.startswith(...)` for platform checks. This is not yet supported in type inference: + +```py +import sys + +reveal_type(sys.platform.startswith("freebsd")) # revealed: @Todo(call todo) +reveal_type(sys.platform.startswith("linux")) # revealed: @Todo(call todo) +``` From d249801bf2bcd8afa30b1ffc1c39a5c65982d28a Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 18 Dec 2024 11:32:56 +0100 Subject: [PATCH 29/65] Implement if-expressions --- .../mdtest/statically_known_branches.md | 59 ++++++++++++++++--- .../src/semantic_index/builder.rs | 31 +++++----- .../src/semantic_index/use_def.rs | 11 +--- .../semantic_index/use_def/symbol_state.rs | 38 ++++++------ .../src/visibility_constraints.rs | 6 +- 5 files changed, 88 insertions(+), 57 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md b/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md index 8cefccc342063..1a588d220d1aa 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md +++ b/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md @@ -41,8 +41,6 @@ if typing.TYPE_CHECKING: def f(s: SomeType) -> None: ... ``` -The rest of this document contains tests for various cases where this feature can be used. - ## Common use cases This section makes sure that we can handle all commonly encountered patterns of static conditions. @@ -164,6 +162,9 @@ other ## If statements +The rest of this document contains tests for various control flow elements. This section tests `if` +statements. + ### Always false #### If @@ -349,7 +350,7 @@ else: reveal_type(x) # revealed: Literal[2, 3, 4] ``` -#### `elif` without `else` branch +#### `elif` without `else` branch, always true ```py def flag() -> bool: ... @@ -364,6 +365,21 @@ elif True: reveal_type(x) # revealed: Literal[2, 3] ``` +#### `elif` without `else` branch, always false + +```py +def flag() -> bool: ... + +x = 1 + +if flag(): + x = 2 +elif False: + x = 3 + +reveal_type(x) # revealed: Literal[1, 2] +``` + ### Nested conditionals #### `if True` inside `if True` @@ -769,22 +785,31 @@ reveal_type(x) # revealed: Literal[3, 4] ## If expressions -See also: tests in [expression/if.md](expression/if.md). +Note that the result type of an `if`-expression can be precisely inferred if the condition is +statically known. This is a plain type inference feature that does not need support for statically +known branches. The tests for this feature are in [expression/if.md](expression/if.md). + +The tests here make sure that we also handle assignment expressions inside `if`-expressions +correctly. + +### Type inference ### Always true ```py -x = 1 if True else 2 +x = (y := 1) if True else (y := 2) reveal_type(x) # revealed: Literal[1] +reveal_type(y) # revealed: Literal[1] ``` ### Always false ```py -x = 1 if False else 2 +x = (y := 1) if False else (y := 2) reveal_type(x) # revealed: Literal[2] +reveal_type(y) # revealed: Literal[2] ``` ## Boolean expressions @@ -795,6 +820,10 @@ reveal_type(x) # revealed: Literal[2] (x := 1) or (x := 2) reveal_type(x) # revealed: Literal[1] + +(y := 1) or (y := 2) or (y := 3) or (y := 4) + +reveal_type(y) # revealed: Literal[1] ``` ### Always true, `and` @@ -803,22 +832,34 @@ reveal_type(x) # revealed: Literal[1] (x := 1) and (x := 2) reveal_type(x) # revealed: Literal[2] + +(y := 1) and (y := 2) and (y := 3) and (y := 4) + +reveal_type(y) # revealed: Literal[4] ``` ### Always false, `or` ```py -(x := 0) or (x := 2) +(x := 0) or (x := 1) -reveal_type(x) # revealed: Literal[2] +reveal_type(x) # revealed: Literal[1] + +(y := 0) or (y := 0) or (y := 1) or (y := 2) + +reveal_type(y) # revealed: Literal[1] ``` ### Always false, `and` ```py -(x := 0) and (x := 2) +(x := 0) and (x := 1) reveal_type(x) # revealed: Literal[0] + +(y := 0) and (y := 1) and (y := 2) and (y := 3) + +reveal_type(y) # revealed: Literal[0] ``` ## While loops diff --git a/crates/red_knot_python_semantic/src/semantic_index/builder.rs b/crates/red_knot_python_semantic/src/semantic_index/builder.rs index 9fb3fae453ca3..acf0fcae96016 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder.rs @@ -296,14 +296,6 @@ impl<'db> SemanticIndexBuilder<'db> { self.current_use_def_map_mut().record_constraint(constraint) } - fn add_visibility_constraint( - &mut self, - constraint: ScopedConstraintId, - ) -> ScopedVisibilityConstraintId { - self.current_use_def_map_mut() - .add_visibility_constraint(VisibilityConstraint::VisibleIf(constraint)) - } - fn record_visibility_constraint( &mut self, constraint: ScopedConstraintId, @@ -1325,14 +1317,17 @@ where }) => { self.visit_expr(test); let pre_if = self.flow_snapshot(); - let (_, constraint) = self.record_expression_constraint(test); + let (constraint_id, constraint) = self.record_expression_constraint(test); self.visit_expr(body); + let visibility_constraint = self.record_visibility_constraint(constraint_id); let post_body = self.flow_snapshot(); - self.flow_restore(pre_if); + self.flow_restore(pre_if.clone()); self.record_negated_constraint(constraint); self.visit_expr(orelse); + self.record_negated_visibility_constraint(visibility_constraint); self.flow_merge(post_body); + self.reset_visibility_constraints(pre_if); } ast::Expr::ListComp( list_comprehension @ ast::ExprListComp { @@ -1392,12 +1387,13 @@ where let pre_op = self.flow_snapshot(); let mut snapshots = vec![]; - let mut constraints = vec![]; + let mut visibility_constraints = vec![]; + let mut last_constraint = None; for (index, value) in values.iter().enumerate() { self.visit_expr(value); - if let Some(last_constraint_id) = constraints.last() { - self.record_visibility_constraint(*last_constraint_id); + if let Some(id) = last_constraint { + visibility_constraints.push(self.record_visibility_constraint(id)); } // Snapshot is taken after visiting the expression but before adding the constraint. snapshots.push(self.flow_snapshot()); @@ -1408,7 +1404,7 @@ where BoolOp::And => self.record_constraint(constraint), BoolOp::Or => self.record_negated_constraint(constraint), }; - constraints.push(id); + last_constraint = Some(id); } } @@ -1416,9 +1412,10 @@ where let first = snapshots.next().expect("at least one value"); self.flow_restore(first); - for id in constraints { - let vid = self.add_visibility_constraint(id); - self.record_negated_visibility_constraint(vid); + // debug_assert(constraints.len() == snapshots.len()); + + for id in visibility_constraints { + self.record_negated_visibility_constraint(id); } for snapshot in snapshots { diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index bd5c24c5c5c13..78045de6b91cf 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -472,7 +472,7 @@ impl<'db> UseDefMapBuilder<'db> { all_definitions: IndexVec::from_iter([None]), all_constraints: IndexVec::new(), visibility_constraints: VisibilityConstraints::new(), - unbound_visibility: ScopedVisibilityConstraintId::ALWAYS_VISIBLE, + unbound_visibility: ScopedVisibilityConstraintId::ALWAYS_TRUE, bindings_by_use: IndexVec::new(), definitions_by_definition: FxHashMap::default(), symbol_states: IndexVec::new(), @@ -504,18 +504,11 @@ impl<'db> UseDefMapBuilder<'db> { constraint_id } - pub(super) fn add_visibility_constraint( - &mut self, - constraint: VisibilityConstraint, - ) -> ScopedVisibilityConstraintId { - self.visibility_constraints.add(constraint) - } - pub(super) fn record_visibility_constraint( &mut self, constraint: VisibilityConstraint, ) -> ScopedVisibilityConstraintId { - let new_constraint_id = self.add_visibility_constraint(constraint); + let new_constraint_id = self.visibility_constraints.add(constraint); for state in &mut self.symbol_states { state.record_visibility_constraint(&mut self.visibility_constraints, new_constraint_id); } diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs index e0f2c224ba520..f473c78942f55 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs @@ -98,7 +98,7 @@ type ConstraintsIntoIterator = smallvec::IntoIter; pub(crate) struct ScopedVisibilityConstraintId; impl ScopedVisibilityConstraintId { - pub(crate) const ALWAYS_VISIBLE: ScopedVisibilityConstraintId = + pub(crate) const ALWAYS_TRUE: ScopedVisibilityConstraintId = ScopedVisibilityConstraintId::from_u32(0); } @@ -135,7 +135,7 @@ impl SymbolDeclarations { self.visibility_constraints = VisibilityConstraintPerBinding::with_capacity(1); self.visibility_constraints - .push(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + .push(ScopedVisibilityConstraintId::ALWAYS_TRUE); } /// Add given visibility constraint to all live bindings. @@ -199,7 +199,7 @@ impl SymbolBindings { self.visibility_constraints = VisibilityConstraintPerBinding::with_capacity(1); self.visibility_constraints - .push(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + .push(ScopedVisibilityConstraintId::ALWAYS_TRUE); } /// Add given constraint to all live bindings. @@ -655,7 +655,7 @@ mod tests { fn unbound() { let constraints = AllConstraints::new(); let visibility_constraints = VisibilityConstraints::new(); - let sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + let sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); assert_bindings(&constraints, &visibility_constraints, &sym, &["unbound<>"]); } @@ -664,7 +664,7 @@ mod tests { fn with() { let constraints = AllConstraints::new(); let visibility_constraints = VisibilityConstraints::new(); - let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym.record_binding(ScopedDefinitionId::from_u32(1)); assert_bindings(&constraints, &visibility_constraints, &sym, &["1<>"]); @@ -674,7 +674,7 @@ mod tests { fn record_constraint() { let constraints = AllConstraints::new(); let visibility_constraints = VisibilityConstraints::new(); - let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym.record_binding(ScopedDefinitionId::from_u32(1)); sym.record_constraint(ScopedConstraintId::from_u32(0)); @@ -687,11 +687,11 @@ mod tests { let mut visibility_constraints = VisibilityConstraints::new(); // merging the same definition with the same constraint keeps the constraint - let mut sym1a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + let mut sym1a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym1a.record_binding(ScopedDefinitionId::from_u32(1)); sym1a.record_constraint(ScopedConstraintId::from_u32(0)); - let mut sym1b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + let mut sym1b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym1b.record_binding(ScopedDefinitionId::from_u32(1)); sym1b.record_constraint(ScopedConstraintId::from_u32(0)); @@ -700,11 +700,11 @@ mod tests { assert_bindings(&constraints, &visibility_constraints, &sym1, &["1<0>"]); // merging the same definition with differing constraints drops all constraints - let mut sym2a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + let mut sym2a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym2a.record_binding(ScopedDefinitionId::from_u32(2)); sym2a.record_constraint(ScopedConstraintId::from_u32(1)); - let mut sym1b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + let mut sym1b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym1b.record_binding(ScopedDefinitionId::from_u32(2)); sym1b.record_constraint(ScopedConstraintId::from_u32(2)); @@ -713,11 +713,11 @@ mod tests { assert_bindings(&constraints, &visibility_constraints, &sym2, &["2<>"]); // merging a constrained definition with unbound keeps both - let mut sym3a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + let mut sym3a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym3a.record_binding(ScopedDefinitionId::from_u32(3)); sym3a.record_constraint(ScopedConstraintId::from_u32(3)); - let sym2b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + let sym2b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym3a.merge(sym2b, &mut visibility_constraints); let sym3 = sym3a; @@ -743,7 +743,7 @@ mod tests { fn no_declaration() { let constraints = AllConstraints::new(); let visibility_constraints = VisibilityConstraints::new(); - let sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + let sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); assert_declarations(&constraints, &visibility_constraints, &sym, &["undeclared"]); } @@ -752,7 +752,7 @@ mod tests { fn record_declaration() { let constraints = AllConstraints::new(); let visibility_constraints = VisibilityConstraints::new(); - let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym.record_declaration(ScopedDefinitionId::from_u32(1)); assert_declarations(&constraints, &visibility_constraints, &sym, &["1"]); @@ -762,7 +762,7 @@ mod tests { fn record_declaration_override() { let constraints = AllConstraints::new(); let visibility_constraints = VisibilityConstraints::new(); - let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym.record_declaration(ScopedDefinitionId::from_u32(1)); sym.record_declaration(ScopedDefinitionId::from_u32(2)); @@ -773,10 +773,10 @@ mod tests { fn record_declaration_merge() { let constraints = AllConstraints::new(); let mut visibility_constraints = VisibilityConstraints::new(); - let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym.record_declaration(ScopedDefinitionId::from_u32(1)); - let mut sym2 = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + let mut sym2 = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym2.record_declaration(ScopedDefinitionId::from_u32(2)); sym.merge(sym2, &mut visibility_constraints); @@ -788,10 +788,10 @@ mod tests { fn record_declaration_merge_partial_undeclared() { let constraints = AllConstraints::new(); let mut visibility_constraints = VisibilityConstraints::new(); - let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym.record_declaration(ScopedDefinitionId::from_u32(1)); - let sym2 = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_VISIBLE); + let sym2 = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym.merge(sym2, &mut visibility_constraints); diff --git a/crates/red_knot_python_semantic/src/visibility_constraints.rs b/crates/red_knot_python_semantic/src/visibility_constraints.rs index 157e746333d36..fa953ec6a076a 100644 --- a/crates/red_knot_python_semantic/src/visibility_constraints.rs +++ b/crates/red_knot_python_semantic/src/visibility_constraints.rs @@ -43,10 +43,10 @@ impl VisibilityConstraints { ) -> ScopedVisibilityConstraintId { match (&self.constraints[a], &self.constraints[b]) { (_, VisibilityConstraint::VisibleIfNot(id)) if a == *id => { - ScopedVisibilityConstraintId::ALWAYS_VISIBLE + ScopedVisibilityConstraintId::ALWAYS_TRUE } (VisibilityConstraint::VisibleIfNot(id), _) if *id == b => { - ScopedVisibilityConstraintId::ALWAYS_VISIBLE + ScopedVisibilityConstraintId::ALWAYS_TRUE } _ => self.add(VisibilityConstraint::Merged(a, b)), } @@ -57,7 +57,7 @@ impl VisibilityConstraints { a: ScopedVisibilityConstraintId, b: ScopedVisibilityConstraintId, ) -> ScopedVisibilityConstraintId { - if a == ScopedVisibilityConstraintId::ALWAYS_VISIBLE { + if a == ScopedVisibilityConstraintId::ALWAYS_TRUE { b } else { self.add(VisibilityConstraint::Sequence(a, b)) From 8b60946875d23d6fe3d278ea5f1efa710526a10c Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 18 Dec 2024 12:45:51 +0100 Subject: [PATCH 30/65] Fix boolean expressions --- .../src/semantic_index/builder.rs | 95 ++++++++++++------- .../src/semantic_index/use_def.rs | 35 +++++-- 2 files changed, 88 insertions(+), 42 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index/builder.rs b/crates/red_knot_python_semantic/src/semantic_index/builder.rs index acf0fcae96016..8feb891343ad6 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder.rs @@ -292,10 +292,54 @@ impl<'db> SemanticIndexBuilder<'db> { (constraint_id, constraint) } + fn build_constraint(&mut self, constraint_node: &Expr) -> Constraint<'db> { + let expression = self.add_standalone_expression(constraint_node); + Constraint { + node: ConstraintNode::Expression(expression), + is_positive: true, + } + } + + fn add_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { + self.current_use_def_map_mut().add_constraint(constraint) + } + + fn add_negated_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { + let negated = Constraint { + node: constraint.node, + is_positive: false, + }; + self.current_use_def_map_mut().add_constraint(negated) + } + + fn record_constraint_id(&mut self, constraint: ScopedConstraintId) { + self.current_use_def_map_mut() + .record_constraint_id(constraint) + } + fn record_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { self.current_use_def_map_mut().record_constraint(constraint) } + fn record_negated_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { + let constraint_id = self.add_negated_constraint(constraint); + self.record_constraint_id(constraint_id); + constraint_id + } + + fn add_visibility_constraint( + &mut self, + constraint: VisibilityConstraint, + ) -> ScopedVisibilityConstraintId { + self.current_use_def_map_mut() + .add_visibility_constraint(constraint) + } + + fn record_visibility_constraint_id(&mut self, constraint: ScopedVisibilityConstraintId) { + self.current_use_def_map_mut() + .record_visibility_constraint_id(constraint) + } + fn record_visibility_constraint( &mut self, constraint: ScopedConstraintId, @@ -317,23 +361,6 @@ impl<'db> SemanticIndexBuilder<'db> { .record_visibility_constraint(VisibilityConstraint::VisibleIfNot(constraint)) } - fn build_constraint(&mut self, constraint_node: &Expr) -> Constraint<'db> { - let expression = self.add_standalone_expression(constraint_node); - Constraint { - node: ConstraintNode::Expression(expression), - is_positive: true, - } - } - - fn record_negated_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { - let negated = Constraint { - node: constraint.node, - is_positive: false, - }; - let constraint_id = self.current_use_def_map_mut().record_constraint(negated); - constraint_id - } - fn push_assignment(&mut self, assignment: CurrentAssignment<'db>) { self.current_assignments.push(assignment); } @@ -1388,34 +1415,32 @@ where let mut snapshots = vec![]; let mut visibility_constraints = vec![]; - let mut last_constraint = None; for (index, value) in values.iter().enumerate() { self.visit_expr(value); - if let Some(id) = last_constraint { - visibility_constraints.push(self.record_visibility_constraint(id)); + + for id in &visibility_constraints { + self.record_visibility_constraint_id(*id); } - // Snapshot is taken after visiting the expression but before adding the constraint. - snapshots.push(self.flow_snapshot()); + + // In the last value we don't need to take a snapshot nor add a constraint if index < values.len() - 1 { - // In the last value we don't need to add a constraint let constraint = self.build_constraint(value); let id = match op { - BoolOp::And => self.record_constraint(constraint), - BoolOp::Or => self.record_negated_constraint(constraint), + BoolOp::And => self.add_constraint(constraint), + BoolOp::Or => self.add_negated_constraint(constraint), }; - last_constraint = Some(id); - } - } - - let mut snapshots = snapshots.into_iter(); - let first = snapshots.next().expect("at least one value"); - self.flow_restore(first); + let visibility_constraint = + self.add_visibility_constraint(VisibilityConstraint::VisibleIf(id)); + visibility_constraints.push(visibility_constraint); + self.record_negated_visibility_constraint(visibility_constraint); - // debug_assert(constraints.len() == snapshots.len()); + // Snapshot is taken after visiting the expression and adding the visibility + // constraint, but before adding the narrowing constraint. + snapshots.push(self.flow_snapshot()); - for id in visibility_constraints { - self.record_negated_visibility_constraint(id); + self.record_constraint_id(id); + } } for snapshot in snapshots { diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index 78045de6b91cf..965cf184c3e87 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -496,27 +496,48 @@ impl<'db> UseDefMapBuilder<'db> { symbol_state.record_binding(def_id); } - pub(super) fn record_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { - let constraint_id = self.all_constraints.push(constraint); + pub(super) fn add_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { + self.all_constraints.push(constraint) + } + + pub(super) fn record_constraint_id(&mut self, constraint: ScopedConstraintId) { for state in &mut self.symbol_states { - state.record_constraint(constraint_id); + state.record_constraint(constraint); } + } + + pub(super) fn record_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { + let constraint_id = self.add_constraint(constraint); + self.record_constraint_id(constraint_id); constraint_id } - pub(super) fn record_visibility_constraint( + pub(super) fn add_visibility_constraint( &mut self, constraint: VisibilityConstraint, ) -> ScopedVisibilityConstraintId { - let new_constraint_id = self.visibility_constraints.add(constraint); + self.visibility_constraints.add(constraint) + } + + pub(super) fn record_visibility_constraint_id( + &mut self, + constraint: ScopedVisibilityConstraintId, + ) { for state in &mut self.symbol_states { - state.record_visibility_constraint(&mut self.visibility_constraints, new_constraint_id); + state.record_visibility_constraint(&mut self.visibility_constraints, constraint); } self.unbound_visibility = self .visibility_constraints - .add_sequence(self.unbound_visibility, new_constraint_id); + .add_sequence(self.unbound_visibility, constraint); + } + pub(super) fn record_visibility_constraint( + &mut self, + constraint: VisibilityConstraint, + ) -> ScopedVisibilityConstraintId { + let new_constraint_id = self.add_visibility_constraint(constraint); + self.record_visibility_constraint_id(new_constraint_id); new_constraint_id } From 55330c4ed7f65e849400e17d2f48c4b1652be56b Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 18 Dec 2024 14:01:32 +0100 Subject: [PATCH 31/65] Revert "Fix boolean expressions" This reverts commit 79a1b6bb52230705c10fe668093316c16444f953. --- .../src/semantic_index/builder.rs | 95 +++++++------------ .../src/semantic_index/use_def.rs | 35 ++----- 2 files changed, 42 insertions(+), 88 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index/builder.rs b/crates/red_knot_python_semantic/src/semantic_index/builder.rs index 8feb891343ad6..acf0fcae96016 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder.rs @@ -292,54 +292,10 @@ impl<'db> SemanticIndexBuilder<'db> { (constraint_id, constraint) } - fn build_constraint(&mut self, constraint_node: &Expr) -> Constraint<'db> { - let expression = self.add_standalone_expression(constraint_node); - Constraint { - node: ConstraintNode::Expression(expression), - is_positive: true, - } - } - - fn add_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { - self.current_use_def_map_mut().add_constraint(constraint) - } - - fn add_negated_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { - let negated = Constraint { - node: constraint.node, - is_positive: false, - }; - self.current_use_def_map_mut().add_constraint(negated) - } - - fn record_constraint_id(&mut self, constraint: ScopedConstraintId) { - self.current_use_def_map_mut() - .record_constraint_id(constraint) - } - fn record_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { self.current_use_def_map_mut().record_constraint(constraint) } - fn record_negated_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { - let constraint_id = self.add_negated_constraint(constraint); - self.record_constraint_id(constraint_id); - constraint_id - } - - fn add_visibility_constraint( - &mut self, - constraint: VisibilityConstraint, - ) -> ScopedVisibilityConstraintId { - self.current_use_def_map_mut() - .add_visibility_constraint(constraint) - } - - fn record_visibility_constraint_id(&mut self, constraint: ScopedVisibilityConstraintId) { - self.current_use_def_map_mut() - .record_visibility_constraint_id(constraint) - } - fn record_visibility_constraint( &mut self, constraint: ScopedConstraintId, @@ -361,6 +317,23 @@ impl<'db> SemanticIndexBuilder<'db> { .record_visibility_constraint(VisibilityConstraint::VisibleIfNot(constraint)) } + fn build_constraint(&mut self, constraint_node: &Expr) -> Constraint<'db> { + let expression = self.add_standalone_expression(constraint_node); + Constraint { + node: ConstraintNode::Expression(expression), + is_positive: true, + } + } + + fn record_negated_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { + let negated = Constraint { + node: constraint.node, + is_positive: false, + }; + let constraint_id = self.current_use_def_map_mut().record_constraint(negated); + constraint_id + } + fn push_assignment(&mut self, assignment: CurrentAssignment<'db>) { self.current_assignments.push(assignment); } @@ -1415,32 +1388,34 @@ where let mut snapshots = vec![]; let mut visibility_constraints = vec![]; + let mut last_constraint = None; for (index, value) in values.iter().enumerate() { self.visit_expr(value); - - for id in &visibility_constraints { - self.record_visibility_constraint_id(*id); + if let Some(id) = last_constraint { + visibility_constraints.push(self.record_visibility_constraint(id)); } - - // In the last value we don't need to take a snapshot nor add a constraint + // Snapshot is taken after visiting the expression but before adding the constraint. + snapshots.push(self.flow_snapshot()); if index < values.len() - 1 { + // In the last value we don't need to add a constraint let constraint = self.build_constraint(value); let id = match op { - BoolOp::And => self.add_constraint(constraint), - BoolOp::Or => self.add_negated_constraint(constraint), + BoolOp::And => self.record_constraint(constraint), + BoolOp::Or => self.record_negated_constraint(constraint), }; - let visibility_constraint = - self.add_visibility_constraint(VisibilityConstraint::VisibleIf(id)); - visibility_constraints.push(visibility_constraint); - self.record_negated_visibility_constraint(visibility_constraint); + last_constraint = Some(id); + } + } - // Snapshot is taken after visiting the expression and adding the visibility - // constraint, but before adding the narrowing constraint. - snapshots.push(self.flow_snapshot()); + let mut snapshots = snapshots.into_iter(); + let first = snapshots.next().expect("at least one value"); + self.flow_restore(first); - self.record_constraint_id(id); - } + // debug_assert(constraints.len() == snapshots.len()); + + for id in visibility_constraints { + self.record_negated_visibility_constraint(id); } for snapshot in snapshots { diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index 965cf184c3e87..78045de6b91cf 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -496,48 +496,27 @@ impl<'db> UseDefMapBuilder<'db> { symbol_state.record_binding(def_id); } - pub(super) fn add_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { - self.all_constraints.push(constraint) - } - - pub(super) fn record_constraint_id(&mut self, constraint: ScopedConstraintId) { + pub(super) fn record_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { + let constraint_id = self.all_constraints.push(constraint); for state in &mut self.symbol_states { - state.record_constraint(constraint); + state.record_constraint(constraint_id); } - } - - pub(super) fn record_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { - let constraint_id = self.add_constraint(constraint); - self.record_constraint_id(constraint_id); constraint_id } - pub(super) fn add_visibility_constraint( + pub(super) fn record_visibility_constraint( &mut self, constraint: VisibilityConstraint, ) -> ScopedVisibilityConstraintId { - self.visibility_constraints.add(constraint) - } - - pub(super) fn record_visibility_constraint_id( - &mut self, - constraint: ScopedVisibilityConstraintId, - ) { + let new_constraint_id = self.visibility_constraints.add(constraint); for state in &mut self.symbol_states { - state.record_visibility_constraint(&mut self.visibility_constraints, constraint); + state.record_visibility_constraint(&mut self.visibility_constraints, new_constraint_id); } self.unbound_visibility = self .visibility_constraints - .add_sequence(self.unbound_visibility, constraint); - } + .add_sequence(self.unbound_visibility, new_constraint_id); - pub(super) fn record_visibility_constraint( - &mut self, - constraint: VisibilityConstraint, - ) -> ScopedVisibilityConstraintId { - let new_constraint_id = self.add_visibility_constraint(constraint); - self.record_visibility_constraint_id(new_constraint_id); new_constraint_id } From 45d544b60109b85f90b588bfc912a4e005c2c7ae Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 18 Dec 2024 14:03:16 +0100 Subject: [PATCH 32/65] Document current limitations --- .../resources/mdtest/statically_known_branches.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md b/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md index 1a588d220d1aa..74487fa1966d5 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md +++ b/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md @@ -835,7 +835,8 @@ reveal_type(x) # revealed: Literal[2] (y := 1) and (y := 2) and (y := 3) and (y := 4) -reveal_type(y) # revealed: Literal[4] +# TODO: should be Literal[4] +reveal_type(y) # revealed: Literal[2, 3, 4] ``` ### Always false, `or` @@ -847,7 +848,8 @@ reveal_type(x) # revealed: Literal[1] (y := 0) or (y := 0) or (y := 1) or (y := 2) -reveal_type(y) # revealed: Literal[1] +# TODO: should be Literal[1] +reveal_type(y) # revealed: Literal[0, 1] ``` ### Always false, `and` @@ -859,7 +861,8 @@ reveal_type(x) # revealed: Literal[0] (y := 0) and (y := 1) and (y := 2) and (y := 3) -reveal_type(y) # revealed: Literal[0] +# TODO: should be Literal[0] +reveal_type(y) # revealed: Literal[2, 3] ``` ## While loops From 6c64ae0c0538c5d27ba5ede80d9b2d65c47ee9ff Mon Sep 17 00:00:00 2001 From: David Peter Date: Wed, 18 Dec 2024 14:11:01 +0100 Subject: [PATCH 33/65] Rename constraints --- .../src/visibility_constraints.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/red_knot_python_semantic/src/visibility_constraints.rs b/crates/red_knot_python_semantic/src/visibility_constraints.rs index fa953ec6a076a..4d60908a4cda8 100644 --- a/crates/red_knot_python_semantic/src/visibility_constraints.rs +++ b/crates/red_knot_python_semantic/src/visibility_constraints.rs @@ -16,8 +16,8 @@ pub(crate) enum VisibilityConstraint { AlwaysTrue, VisibleIf(ScopedConstraintId), VisibleIfNot(ScopedVisibilityConstraintId), - Sequence(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), - Merged(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), + KleeneAnd(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), + KleeneOr(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), } #[derive(Debug, PartialEq, Eq)] @@ -48,7 +48,7 @@ impl VisibilityConstraints { (VisibilityConstraint::VisibleIfNot(id), _) if *id == b => { ScopedVisibilityConstraintId::ALWAYS_TRUE } - _ => self.add(VisibilityConstraint::Merged(a, b)), + _ => self.add(VisibilityConstraint::KleeneOr(a, b)), } } @@ -60,7 +60,7 @@ impl VisibilityConstraints { if a == ScopedVisibilityConstraintId::ALWAYS_TRUE { b } else { - self.add(VisibilityConstraint::Sequence(a, b)) + self.add(VisibilityConstraint::KleeneAnd(a, b)) } } @@ -94,7 +94,7 @@ impl VisibilityConstraints { VisibilityConstraint::VisibleIfNot(negated) => self .analyze_impl(db, constraints, *negated, max_depth - 1) .negate(), - VisibilityConstraint::Sequence(lhs, rhs) => { + VisibilityConstraint::KleeneAnd(lhs, rhs) => { let lhs = self.analyze_impl(db, constraints, *lhs, max_depth - 1); if lhs == Truthiness::AlwaysFalse { @@ -111,7 +111,7 @@ impl VisibilityConstraints { Truthiness::Ambiguous } } - VisibilityConstraint::Merged(lhs_id, rhs_id) => { + VisibilityConstraint::KleeneOr(lhs_id, rhs_id) => { let lhs = self.analyze_impl(db, constraints, *lhs_id, max_depth - 1); if lhs == Truthiness::AlwaysTrue { From 2bbd586725210a6ffea4ae64d8794cd8d8b493bc Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 19 Dec 2024 08:39:04 +0100 Subject: [PATCH 34/65] Rename constraints --- .../src/semantic_index/use_def.rs | 4 ++-- .../src/semantic_index/use_def/symbol_state.rs | 10 ++++++---- .../src/visibility_constraints.rs | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index 78045de6b91cf..88ca347e70228 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -515,7 +515,7 @@ impl<'db> UseDefMapBuilder<'db> { self.unbound_visibility = self .visibility_constraints - .add_sequence(self.unbound_visibility, new_constraint_id); + .add_and_constraint(self.unbound_visibility, new_constraint_id); new_constraint_id } @@ -622,7 +622,7 @@ impl<'db> UseDefMapBuilder<'db> { self.unbound_visibility = self .visibility_constraints - .add_merged(self.unbound_visibility, snapshot.unbound_visibility); + .add_or_constraint(self.unbound_visibility, snapshot.unbound_visibility); } pub(super) fn finish(mut self) -> UseDefMap<'db> { diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs index f473c78942f55..61add28854224 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs @@ -145,7 +145,7 @@ impl SymbolDeclarations { constraint: ScopedVisibilityConstraintId, ) { for existing in &mut self.visibility_constraints { - *existing = visibility_constraints.add_sequence(*existing, constraint); + *existing = visibility_constraints.add_and_constraint(*existing, constraint); } } @@ -216,7 +216,7 @@ impl SymbolBindings { constraint: ScopedVisibilityConstraintId, ) { for existing in &mut self.visibility_constraints { - *existing = visibility_constraints.add_sequence(*existing, constraint); + *existing = visibility_constraints.add_and_constraint(*existing, constraint); } } @@ -404,7 +404,8 @@ impl SymbolState { .next() .expect("visibility_constraints length mismatch"); let current = self.bindings.visibility_constraints.last_mut().unwrap(); - *current = visibility_constraints.add_merged(*current, a_vis_constraint); + *current = + visibility_constraints.add_or_constraint(*current, a_vis_constraint); opt_a_def = a_defs_iter.next(); opt_b_def = b_defs_iter.next(); @@ -470,7 +471,8 @@ impl SymbolState { .next() .expect("declarations and visibility_constraints length mismatch"); let current = self.declarations.visibility_constraints.last_mut().unwrap(); - *current = visibility_constraints.add_merged(*current, a_vis_constraint); + *current = + visibility_constraints.add_or_constraint(*current, a_vis_constraint); opt_a_decl = a_decls_iter.next(); opt_b_decl = b_decls_iter.next(); diff --git a/crates/red_knot_python_semantic/src/visibility_constraints.rs b/crates/red_knot_python_semantic/src/visibility_constraints.rs index 4d60908a4cda8..1749bbce7417a 100644 --- a/crates/red_knot_python_semantic/src/visibility_constraints.rs +++ b/crates/red_knot_python_semantic/src/visibility_constraints.rs @@ -36,7 +36,7 @@ impl VisibilityConstraints { self.constraints.push(constraint) } - pub(crate) fn add_merged( + pub(crate) fn add_or_constraint( &mut self, a: ScopedVisibilityConstraintId, b: ScopedVisibilityConstraintId, @@ -52,7 +52,7 @@ impl VisibilityConstraints { } } - pub(crate) fn add_sequence( + pub(crate) fn add_and_constraint( &mut self, a: ScopedVisibilityConstraintId, b: ScopedVisibilityConstraintId, From c5e32d937ec948ca1111a55778615af092a535bd Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 19 Dec 2024 10:51:14 +0100 Subject: [PATCH 35/65] Finally!! --- .../mdtest/statically_known_branches.md | 9 +- .../src/semantic_index/builder.rs | 107 ++++++++++++------ .../src/semantic_index/use_def.rs | 35 ++++-- 3 files changed, 103 insertions(+), 48 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md b/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md index 74487fa1966d5..1a588d220d1aa 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md +++ b/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md @@ -835,8 +835,7 @@ reveal_type(x) # revealed: Literal[2] (y := 1) and (y := 2) and (y := 3) and (y := 4) -# TODO: should be Literal[4] -reveal_type(y) # revealed: Literal[2, 3, 4] +reveal_type(y) # revealed: Literal[4] ``` ### Always false, `or` @@ -848,8 +847,7 @@ reveal_type(x) # revealed: Literal[1] (y := 0) or (y := 0) or (y := 1) or (y := 2) -# TODO: should be Literal[1] -reveal_type(y) # revealed: Literal[0, 1] +reveal_type(y) # revealed: Literal[1] ``` ### Always false, `and` @@ -861,8 +859,7 @@ reveal_type(x) # revealed: Literal[0] (y := 0) and (y := 1) and (y := 2) and (y := 3) -# TODO: should be Literal[0] -reveal_type(y) # revealed: Literal[2, 3] +reveal_type(y) # revealed: Literal[0] ``` ## While loops diff --git a/crates/red_knot_python_semantic/src/semantic_index/builder.rs b/crates/red_knot_python_semantic/src/semantic_index/builder.rs index acf0fcae96016..62fbbd2649ae5 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder.rs @@ -292,10 +292,54 @@ impl<'db> SemanticIndexBuilder<'db> { (constraint_id, constraint) } + fn build_constraint(&mut self, constraint_node: &Expr) -> Constraint<'db> { + let expression = self.add_standalone_expression(constraint_node); + Constraint { + node: ConstraintNode::Expression(expression), + is_positive: true, + } + } + + fn add_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { + self.current_use_def_map_mut().add_constraint(constraint) + } + + fn add_negated_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { + let negated = Constraint { + node: constraint.node, + is_positive: false, + }; + self.current_use_def_map_mut().add_constraint(negated) + } + + fn record_constraint_id(&mut self, constraint: ScopedConstraintId) { + self.current_use_def_map_mut() + .record_constraint_id(constraint) + } + fn record_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { self.current_use_def_map_mut().record_constraint(constraint) } + fn record_negated_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { + let constraint_id = self.add_negated_constraint(constraint); + self.record_constraint_id(constraint_id); + constraint_id + } + + fn add_visibility_constraint( + &mut self, + constraint: VisibilityConstraint, + ) -> ScopedVisibilityConstraintId { + self.current_use_def_map_mut() + .add_visibility_constraint(constraint) + } + + fn record_visibility_constraint_id(&mut self, constraint: ScopedVisibilityConstraintId) { + self.current_use_def_map_mut() + .record_visibility_constraint_id(constraint) + } + fn record_visibility_constraint( &mut self, constraint: ScopedConstraintId, @@ -317,23 +361,6 @@ impl<'db> SemanticIndexBuilder<'db> { .record_visibility_constraint(VisibilityConstraint::VisibleIfNot(constraint)) } - fn build_constraint(&mut self, constraint_node: &Expr) -> Constraint<'db> { - let expression = self.add_standalone_expression(constraint_node); - Constraint { - node: ConstraintNode::Expression(expression), - is_positive: true, - } - } - - fn record_negated_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { - let negated = Constraint { - node: constraint.node, - is_positive: false, - }; - let constraint_id = self.current_use_def_map_mut().record_constraint(negated); - constraint_id - } - fn push_assignment(&mut self, assignment: CurrentAssignment<'db>) { self.current_assignments.push(assignment); } @@ -1388,34 +1415,44 @@ where let mut snapshots = vec![]; let mut visibility_constraints = vec![]; - let mut last_constraint = None; for (index, value) in values.iter().enumerate() { self.visit_expr(value); - if let Some(id) = last_constraint { - visibility_constraints.push(self.record_visibility_constraint(id)); + + for vid in &visibility_constraints { + self.record_visibility_constraint_id(*vid); } - // Snapshot is taken after visiting the expression but before adding the constraint. - snapshots.push(self.flow_snapshot()); + + // For the last value, we don't need to model control flow. There is short-circuiting + // anymore. if index < values.len() - 1 { - // In the last value we don't need to add a constraint let constraint = self.build_constraint(value); let id = match op { - BoolOp::And => self.record_constraint(constraint), - BoolOp::Or => self.record_negated_constraint(constraint), + BoolOp::And => self.add_constraint(constraint), + BoolOp::Or => self.add_negated_constraint(constraint), }; - last_constraint = Some(id); - } - } + let visibility_constraint = + self.add_visibility_constraint(VisibilityConstraint::VisibleIf(id)); - let mut snapshots = snapshots.into_iter(); - let first = snapshots.next().expect("at least one value"); - self.flow_restore(first); + let after_expr = self.flow_snapshot(); - // debug_assert(constraints.len() == snapshots.len()); - - for id in visibility_constraints { - self.record_negated_visibility_constraint(id); + // We first model the short-circuiting behavior. We take the short-circuit + // path here if all of the previous short-circuit paths were not taken, so + // we record all previously existing visibility constraints, and negate the + // one for the current expression. + for vid in &visibility_constraints { + self.record_visibility_constraint_id(*vid); + } + self.record_negated_visibility_constraint(visibility_constraint); + snapshots.push(self.flow_snapshot()); + + // Then we model the non-short-circuiting behavior. Here, we need to delay + // the application of the visibility constraint until after the expression + // has been evaluated, so we only push it onto the stack here. + self.flow_restore(after_expr); + self.record_constraint_id(id); + visibility_constraints.push(visibility_constraint); + } } for snapshot in snapshots { diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index 88ca347e70228..bd178aeb6b363 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -496,27 +496,48 @@ impl<'db> UseDefMapBuilder<'db> { symbol_state.record_binding(def_id); } - pub(super) fn record_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { - let constraint_id = self.all_constraints.push(constraint); + pub(super) fn add_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { + self.all_constraints.push(constraint) + } + + pub(super) fn record_constraint_id(&mut self, constraint: ScopedConstraintId) { for state in &mut self.symbol_states { - state.record_constraint(constraint_id); + state.record_constraint(constraint); } + } + + pub(super) fn record_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { + let constraint_id = self.add_constraint(constraint); + self.record_constraint_id(constraint_id); constraint_id } - pub(super) fn record_visibility_constraint( + pub(super) fn add_visibility_constraint( &mut self, constraint: VisibilityConstraint, ) -> ScopedVisibilityConstraintId { - let new_constraint_id = self.visibility_constraints.add(constraint); + self.visibility_constraints.add(constraint) + } + + pub(super) fn record_visibility_constraint_id( + &mut self, + constraint: ScopedVisibilityConstraintId, + ) { for state in &mut self.symbol_states { - state.record_visibility_constraint(&mut self.visibility_constraints, new_constraint_id); + state.record_visibility_constraint(&mut self.visibility_constraints, constraint); } self.unbound_visibility = self .visibility_constraints - .add_and_constraint(self.unbound_visibility, new_constraint_id); + .add_and_constraint(self.unbound_visibility, constraint); + } + pub(super) fn record_visibility_constraint( + &mut self, + constraint: VisibilityConstraint, + ) -> ScopedVisibilityConstraintId { + let new_constraint_id = self.add_visibility_constraint(constraint); + self.record_visibility_constraint_id(new_constraint_id); new_constraint_id } From 2beabc61c2469496c57a87f7bed1429502b5365f Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 19 Dec 2024 12:06:53 +0100 Subject: [PATCH 36/65] Minor renamings --- .../src/semantic_index/builder.rs | 6 ++--- .../semantic_index/use_def/symbol_state.rs | 20 +++++++++-------- crates/red_knot_python_semantic/src/types.rs | 4 ++-- .../src/visibility_constraints.rs | 22 ++++++++++--------- 4 files changed, 28 insertions(+), 24 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index/builder.rs b/crates/red_knot_python_semantic/src/semantic_index/builder.rs index 62fbbd2649ae5..5566e278466f5 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder.rs @@ -859,7 +859,7 @@ where self.visit_expr(&node.test); let pre_if = self.flow_snapshot(); let (constraint_id, constraint) = self.record_expression_constraint(&node.test); - let mut constraints = vec![(constraint_id, constraint)]; + let mut constraints = vec![constraint]; self.visit_body(&node.body); let visibility_constraint_id = self.record_visibility_constraint(constraint_id); @@ -888,7 +888,7 @@ where // we can only take an elif/else branch if none of the previous ones were // taken, so the block entry state is always `pre_if` self.flow_restore(pre_if.clone()); - for (_, constraint) in &constraints { + for constraint in &constraints { self.record_negated_constraint(*constraint); } @@ -896,7 +896,7 @@ where self.visit_expr(elif_test); let (constraint_id, constraint) = self.record_expression_constraint(elif_test); - constraints.push((constraint_id, constraint)); + constraints.push(constraint); Some(constraint_id) } else { None diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs index 61add28854224..da732abe7f217 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs @@ -105,7 +105,7 @@ impl ScopedVisibilityConstraintId { const INLINE_VISIBILITY_CONSTRAINTS: usize = 4; type InlineVisibilityConstraintsArray = [ScopedVisibilityConstraintId; INLINE_VISIBILITY_CONSTRAINTS]; -type VisibilityConstraintPerBinding = SmallVec; +type VisibilityConstraintPerDefinition = SmallVec; type VisibilityConstraintsIterator<'a> = std::slice::Iter<'a, ScopedVisibilityConstraintId>; type VisibilityConstraintsIntoIterator = smallvec::IntoIter; @@ -116,14 +116,14 @@ pub(super) struct SymbolDeclarations { pub(crate) live_declarations: Declarations, /// For each live declaration, which visibility constraints apply to it? - pub(crate) visibility_constraints: VisibilityConstraintPerBinding, + pub(crate) visibility_constraints: VisibilityConstraintPerDefinition, } impl SymbolDeclarations { fn undeclared(undeclared_visibility: ScopedVisibilityConstraintId) -> Self { Self { live_declarations: Declarations::with(0), - visibility_constraints: VisibilityConstraintPerBinding::from_iter([ + visibility_constraints: VisibilityConstraintPerDefinition::from_iter([ undeclared_visibility, ]), } @@ -133,7 +133,7 @@ impl SymbolDeclarations { fn record_declaration(&mut self, declaration_id: ScopedDefinitionId) { self.live_declarations = Declarations::with(declaration_id.into()); - self.visibility_constraints = VisibilityConstraintPerBinding::with_capacity(1); + self.visibility_constraints = VisibilityConstraintPerDefinition::with_capacity(1); self.visibility_constraints .push(ScopedVisibilityConstraintId::ALWAYS_TRUE); } @@ -177,7 +177,7 @@ pub(super) struct SymbolBindings { constraints: ConstraintsPerBinding, /// For each live binding, which visibility constraints apply to it? - visibility_constraints: VisibilityConstraintPerBinding, + visibility_constraints: VisibilityConstraintPerDefinition, } impl SymbolBindings { @@ -185,7 +185,9 @@ impl SymbolBindings { Self { live_bindings: Bindings::with(0), constraints: ConstraintsPerBinding::from_iter([Constraints::default()]), - visibility_constraints: VisibilityConstraintPerBinding::from_iter([unbound_visibility]), + visibility_constraints: VisibilityConstraintPerDefinition::from_iter([ + unbound_visibility, + ]), } } @@ -197,7 +199,7 @@ impl SymbolBindings { self.constraints = ConstraintsPerBinding::with_capacity(1); self.constraints.push(Constraints::default()); - self.visibility_constraints = VisibilityConstraintPerBinding::with_capacity(1); + self.visibility_constraints = VisibilityConstraintPerDefinition::with_capacity(1); self.visibility_constraints .push(ScopedVisibilityConstraintId::ALWAYS_TRUE); } @@ -299,11 +301,11 @@ impl SymbolState { bindings: SymbolBindings { live_bindings: Bindings::default(), constraints: ConstraintsPerBinding::default(), - visibility_constraints: VisibilityConstraintPerBinding::default(), + visibility_constraints: VisibilityConstraintPerDefinition::default(), }, declarations: SymbolDeclarations { live_declarations: self.declarations.live_declarations.clone(), - visibility_constraints: VisibilityConstraintPerBinding::default(), + visibility_constraints: VisibilityConstraintPerDefinition::default(), }, }; diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 6b289903d2073..a7f5bcd225889 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -278,7 +278,7 @@ fn bindings_ty<'db>( } in bindings_with_constraints { let static_visibility = - visibility_constraints.analyze(db, all_constraints, visibility_constraint); + visibility_constraints.evaluate(db, all_constraints, visibility_constraint); let Some(binding) = binding else { if !static_visibility.is_always_false() { @@ -361,7 +361,7 @@ fn declarations_ty<'db>( declarations { let static_visibility = - visibility_constraints.analyze(db, all_constraints, visibility_constraint); + visibility_constraints.evaluate(db, all_constraints, visibility_constraint); let Some(declaration) = declaration else { if !static_visibility.is_always_false() { diff --git a/crates/red_knot_python_semantic/src/visibility_constraints.rs b/crates/red_knot_python_semantic/src/visibility_constraints.rs index 1749bbce7417a..2469b67c65964 100644 --- a/crates/red_knot_python_semantic/src/visibility_constraints.rs +++ b/crates/red_knot_python_semantic/src/visibility_constraints.rs @@ -59,23 +59,25 @@ impl VisibilityConstraints { ) -> ScopedVisibilityConstraintId { if a == ScopedVisibilityConstraintId::ALWAYS_TRUE { b + } else if b == ScopedVisibilityConstraintId::ALWAYS_TRUE { + a } else { self.add(VisibilityConstraint::KleeneAnd(a, b)) } } /// Analyze the statically known visibility for a given visibility constraint. - pub(crate) fn analyze<'db>( - self: &VisibilityConstraints, + pub(crate) fn evaluate<'db>( + &self, db: &'db dyn Db, all_constraints: &AllConstraints<'db>, id: ScopedVisibilityConstraintId, ) -> Truthiness { - self.analyze_impl(db, all_constraints, id, MAX_RECURSION_DEPTH) + self.evaluate_impl(db, all_constraints, id, MAX_RECURSION_DEPTH) } - fn analyze_impl<'db>( - self: &VisibilityConstraints, + fn evaluate_impl<'db>( + &self, db: &'db dyn Db, constraints: &AllConstraints<'db>, id: ScopedVisibilityConstraintId, @@ -92,16 +94,16 @@ impl VisibilityConstraints { Self::analyze_single(db, &constraints[*single]) } VisibilityConstraint::VisibleIfNot(negated) => self - .analyze_impl(db, constraints, *negated, max_depth - 1) + .evaluate_impl(db, constraints, *negated, max_depth - 1) .negate(), VisibilityConstraint::KleeneAnd(lhs, rhs) => { - let lhs = self.analyze_impl(db, constraints, *lhs, max_depth - 1); + let lhs = self.evaluate_impl(db, constraints, *lhs, max_depth - 1); if lhs == Truthiness::AlwaysFalse { return Truthiness::AlwaysFalse; } - let rhs = self.analyze_impl(db, constraints, *rhs, max_depth - 1); + let rhs = self.evaluate_impl(db, constraints, *rhs, max_depth - 1); if rhs == Truthiness::AlwaysFalse { Truthiness::AlwaysFalse @@ -112,13 +114,13 @@ impl VisibilityConstraints { } } VisibilityConstraint::KleeneOr(lhs_id, rhs_id) => { - let lhs = self.analyze_impl(db, constraints, *lhs_id, max_depth - 1); + let lhs = self.evaluate_impl(db, constraints, *lhs_id, max_depth - 1); if lhs == Truthiness::AlwaysTrue { return Truthiness::AlwaysTrue; } - let rhs = self.analyze_impl(db, constraints, *rhs_id, max_depth - 1); + let rhs = self.evaluate_impl(db, constraints, *rhs_id, max_depth - 1); if rhs == Truthiness::AlwaysTrue { Truthiness::AlwaysTrue From 0c6c0ee529685913050a152f61df438ad6db4ada Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 19 Dec 2024 12:15:04 +0100 Subject: [PATCH 37/65] Clippy --- crates/red_knot_python_semantic/src/semantic_index/builder.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index/builder.rs b/crates/red_knot_python_semantic/src/semantic_index/builder.rs index 5566e278466f5..55e11063b7674 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder.rs @@ -314,7 +314,7 @@ impl<'db> SemanticIndexBuilder<'db> { fn record_constraint_id(&mut self, constraint: ScopedConstraintId) { self.current_use_def_map_mut() - .record_constraint_id(constraint) + .record_constraint_id(constraint); } fn record_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { @@ -337,7 +337,7 @@ impl<'db> SemanticIndexBuilder<'db> { fn record_visibility_constraint_id(&mut self, constraint: ScopedVisibilityConstraintId) { self.current_use_def_map_mut() - .record_visibility_constraint_id(constraint) + .record_visibility_constraint_id(constraint); } fn record_visibility_constraint( From 32390f3710f8dec346fbb6a5c9e95da3193c333b Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 19 Dec 2024 12:33:10 +0100 Subject: [PATCH 38/65] Fix doc comments --- crates/red_knot_python_semantic/src/semantic_index/use_def.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index bd178aeb6b363..db2091cc20b75 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -249,7 +249,7 @@ pub(crate) struct UseDefMap<'db> { /// Array of [`Constraint`] in this scope. all_constraints: AllConstraints<'db>, - /// Array of [`VisibilityConstraintRef`] in this scope. + /// Array of [`VisibilityConstraint`]s in this scope. visibility_constraints: VisibilityConstraints, /// [`SymbolBindings`] reaching a [`ScopedUseId`]. @@ -449,7 +449,7 @@ pub(super) struct UseDefMapBuilder<'db> { /// Append-only array of [`Constraint`]. all_constraints: AllConstraints<'db>, - /// Append-only array of [`VisibilityConstraintRef`]. + /// Append-only array of [`VisibilityConstraint`]. visibility_constraints: VisibilityConstraints, /// A constraint which describes the visibility of the unbound/undeclared state, i.e. From 8dd5cc5b3efabf50f5f43deb3d4521b7d0b37e6d Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 19 Dec 2024 12:41:58 +0100 Subject: [PATCH 39/65] Add cross-module test to show that result is based on type inference --- .../mdtest/statically_known_branches.md | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md b/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md index 1a588d220d1aa..ea3c1f50d12bc 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md +++ b/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md @@ -160,6 +160,34 @@ both_checks_true other ``` +## Based on type inference + +For the the rest of this test suite, we will mostly use `True` and `False` literals to indicate +statically known conditions, but here, we show that the results are truly based on type inference, +not some special handling of specific conditions in semantic index building. We use two modules to +demonstrate this, since semantic index building is inherently single-module: + +```py path=module.py +class AlwaysTrue: + def __bool__(self) -> Literal[True]: + return True +``` + +```py +from module import AlwaysTrue + +if AlwaysTrue(): + yes = True +else: + no = True + +# no error +yes + +# error: [unresolved-reference] +no +``` + ## If statements The rest of this document contains tests for various control flow elements. This section tests `if` From c3d34378460087c45e9fde56bed0f98a3ad80792 Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 19 Dec 2024 13:19:15 +0100 Subject: [PATCH 40/65] Rename --- .../semantic_index/use_def/symbol_state.rs | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs index da732abe7f217..da861efe40f51 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs @@ -105,7 +105,8 @@ impl ScopedVisibilityConstraintId { const INLINE_VISIBILITY_CONSTRAINTS: usize = 4; type InlineVisibilityConstraintsArray = [ScopedVisibilityConstraintId; INLINE_VISIBILITY_CONSTRAINTS]; -type VisibilityConstraintPerDefinition = SmallVec; +type VisibilityConstraintPerDeclaration = SmallVec; +type VisibilityConstraintPerBinding = SmallVec; type VisibilityConstraintsIterator<'a> = std::slice::Iter<'a, ScopedVisibilityConstraintId>; type VisibilityConstraintsIntoIterator = smallvec::IntoIter; @@ -116,14 +117,14 @@ pub(super) struct SymbolDeclarations { pub(crate) live_declarations: Declarations, /// For each live declaration, which visibility constraints apply to it? - pub(crate) visibility_constraints: VisibilityConstraintPerDefinition, + pub(crate) visibility_constraints: VisibilityConstraintPerDeclaration, } impl SymbolDeclarations { fn undeclared(undeclared_visibility: ScopedVisibilityConstraintId) -> Self { Self { live_declarations: Declarations::with(0), - visibility_constraints: VisibilityConstraintPerDefinition::from_iter([ + visibility_constraints: VisibilityConstraintPerDeclaration::from_iter([ undeclared_visibility, ]), } @@ -133,7 +134,7 @@ impl SymbolDeclarations { fn record_declaration(&mut self, declaration_id: ScopedDefinitionId) { self.live_declarations = Declarations::with(declaration_id.into()); - self.visibility_constraints = VisibilityConstraintPerDefinition::with_capacity(1); + self.visibility_constraints = VisibilityConstraintPerDeclaration::with_capacity(1); self.visibility_constraints .push(ScopedVisibilityConstraintId::ALWAYS_TRUE); } @@ -177,7 +178,7 @@ pub(super) struct SymbolBindings { constraints: ConstraintsPerBinding, /// For each live binding, which visibility constraints apply to it? - visibility_constraints: VisibilityConstraintPerDefinition, + visibility_constraints: VisibilityConstraintPerBinding, } impl SymbolBindings { @@ -185,9 +186,7 @@ impl SymbolBindings { Self { live_bindings: Bindings::with(0), constraints: ConstraintsPerBinding::from_iter([Constraints::default()]), - visibility_constraints: VisibilityConstraintPerDefinition::from_iter([ - unbound_visibility, - ]), + visibility_constraints: VisibilityConstraintPerBinding::from_iter([unbound_visibility]), } } @@ -199,7 +198,7 @@ impl SymbolBindings { self.constraints = ConstraintsPerBinding::with_capacity(1); self.constraints.push(Constraints::default()); - self.visibility_constraints = VisibilityConstraintPerDefinition::with_capacity(1); + self.visibility_constraints = VisibilityConstraintPerBinding::with_capacity(1); self.visibility_constraints .push(ScopedVisibilityConstraintId::ALWAYS_TRUE); } @@ -301,11 +300,11 @@ impl SymbolState { bindings: SymbolBindings { live_bindings: Bindings::default(), constraints: ConstraintsPerBinding::default(), - visibility_constraints: VisibilityConstraintPerDefinition::default(), + visibility_constraints: VisibilityConstraintPerBinding::default(), }, declarations: SymbolDeclarations { live_declarations: self.declarations.live_declarations.clone(), - visibility_constraints: VisibilityConstraintPerDefinition::default(), + visibility_constraints: VisibilityConstraintPerDeclaration::default(), }, }; From 167337b64784227964486eb5688bcbae44c48d6c Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 19 Dec 2024 13:35:12 +0100 Subject: [PATCH 41/65] Simplify iterators --- .../src/semantic_index/use_def.rs | 36 ++--- .../semantic_index/use_def/symbol_state.rs | 125 ++++-------------- 2 files changed, 49 insertions(+), 112 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index db2091cc20b75..92086db2b9a4b 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -328,7 +328,8 @@ impl<'db> UseDefMap<'db> { BindingWithConstraintsIterator { all_definitions: &self.all_definitions, all_constraints: &self.all_constraints, - inner: bindings.iter(&self.all_constraints, &self.visibility_constraints), + all_visibility_constraints: &self.visibility_constraints, + inner: bindings.iter(), } } @@ -338,7 +339,9 @@ impl<'db> UseDefMap<'db> { ) -> DeclarationsIterator<'map, 'db> { DeclarationsIterator { all_definitions: &self.all_definitions, - inner: declarations.iter(&self.all_constraints, &self.visibility_constraints), + all_constraints: &self.all_constraints, + visibility_constraints: &self.visibility_constraints, + inner: declarations.iter(), } } } @@ -354,7 +357,8 @@ enum SymbolDefinitions { pub(crate) struct BindingWithConstraintsIterator<'map, 'db> { all_definitions: &'map IndexVec>>, all_constraints: &'map AllConstraints<'db>, - inner: BindingIdWithConstraintsIterator<'map, 'db>, + all_visibility_constraints: &'map VisibilityConstraints, + inner: BindingIdWithConstraintsIterator<'map>, } impl<'map, 'db> Iterator for BindingWithConstraintsIterator<'map, 'db> { @@ -371,8 +375,8 @@ impl<'map, 'db> Iterator for BindingWithConstraintsIterator<'map, 'db> { all_constraints, constraint_ids: binding_id_with_constraints.constraint_ids, }, - all_constraints: binding_id_with_constraints.all_constraints, - visibility_constraints: binding_id_with_constraints.visibility_constraints, + all_constraints: self.all_constraints, + visibility_constraints: self.all_visibility_constraints, visibility_constraint: binding_id_with_constraints.visibility_constraint, }) } @@ -407,7 +411,9 @@ impl std::iter::FusedIterator for ConstraintsIterator<'_, '_> {} pub(crate) struct DeclarationsIterator<'map, 'db> { all_definitions: &'map IndexVec>>, - inner: DeclarationIdIterator<'map, 'db>, + all_constraints: &'map AllConstraints<'db>, + visibility_constraints: &'map VisibilityConstraints, + inner: DeclarationIdIterator<'map>, } impl<'map, 'db> Iterator for DeclarationsIterator<'map, 'db> { @@ -419,16 +425,14 @@ impl<'map, 'db> Iterator for DeclarationsIterator<'map, 'db> { ); fn next(&mut self) -> Option { - self.inner.next().map( - |(def_id, all_constraints, visibility_constraints, visibility_constraint_id)| { - ( - self.all_definitions[def_id], - all_constraints, - visibility_constraints, - visibility_constraint_id, - ) - }, - ) + self.inner.next().map(|(def_id, visibility_constraint_id)| { + ( + self.all_definitions[def_id], + self.all_constraints, + self.visibility_constraints, + visibility_constraint_id, + ) + }) } } diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs index da861efe40f51..cf046e542b3da 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs @@ -43,7 +43,7 @@ //! //! Tracking live declarations is simpler, since constraints are not involved, but otherwise very //! similar to tracking live bindings. -use crate::semantic_index::{use_def::VisibilityConstraints, AllConstraints}; +use crate::semantic_index::use_def::VisibilityConstraints; use super::bitset::{BitSet, BitSetIterator}; use ruff_index::newtype_index; @@ -151,14 +151,8 @@ impl SymbolDeclarations { } /// Return an iterator over live declarations for this symbol. - pub(super) fn iter<'map, 'db>( - &'map self, - all_constraints: &'map AllConstraints<'db>, - visibility_constraints: &'map VisibilityConstraints, - ) -> DeclarationIdIterator<'map, 'db> { + pub(super) fn iter<'map, 'db>(&'map self) -> DeclarationIdIterator<'map> { DeclarationIdIterator { - all_constraints, - visibility_constraints, declarations_iter: self.live_declarations.iter(), visibility_constraints_iter: self.visibility_constraints.iter(), } @@ -222,14 +216,8 @@ impl SymbolBindings { } /// Iterate over currently live bindings for this symbol - pub(super) fn iter<'map, 'db>( - &'map self, - all_constraints: &'map AllConstraints<'db>, - visibility_constraints: &'map VisibilityConstraints, - ) -> BindingIdWithConstraintsIterator<'map, 'db> { + pub(super) fn iter<'map>(&'map self) -> BindingIdWithConstraintsIterator<'map> { BindingIdWithConstraintsIterator { - all_constraints, - visibility_constraints, definitions: self.live_bindings.iter(), constraints: self.constraints.iter(), visibility_constraints_iter: self.visibility_constraints.iter(), @@ -504,25 +492,21 @@ impl SymbolState { /// A single binding (as [`ScopedDefinitionId`]) with an iterator of its applicable /// [`ScopedConstraintId`]. #[derive(Debug)] -pub(super) struct BindingIdWithConstraints<'map, 'db> { +pub(super) struct BindingIdWithConstraints<'map> { pub(super) definition: ScopedDefinitionId, pub(super) constraint_ids: ConstraintIdIterator<'map>, - pub(super) all_constraints: &'map AllConstraints<'db>, - pub(super) visibility_constraints: &'map VisibilityConstraints, pub(super) visibility_constraint: ScopedVisibilityConstraintId, } #[derive(Debug)] -pub(super) struct BindingIdWithConstraintsIterator<'map, 'db> { - all_constraints: &'map AllConstraints<'db>, - visibility_constraints: &'map VisibilityConstraints, +pub(super) struct BindingIdWithConstraintsIterator<'map> { definitions: BindingsIterator<'map>, constraints: ConstraintsIterator<'map>, visibility_constraints_iter: VisibilityConstraintsIterator<'map>, } -impl<'map, 'db> Iterator for BindingIdWithConstraintsIterator<'map, 'db> { - type Item = BindingIdWithConstraints<'map, 'db>; +impl<'map, 'db> Iterator for BindingIdWithConstraintsIterator<'map> { + type Item = BindingIdWithConstraints<'map>; fn next(&mut self) -> Option { match ( @@ -537,8 +521,6 @@ impl<'map, 'db> Iterator for BindingIdWithConstraintsIterator<'map, 'db> { constraint_ids: ConstraintIdIterator { wrapped: constraints.iter(), }, - all_constraints: self.all_constraints, - visibility_constraints: self.visibility_constraints, visibility_constraint: *visibility_constraint_id, }) } @@ -548,7 +530,7 @@ impl<'map, 'db> Iterator for BindingIdWithConstraintsIterator<'map, 'db> { } } -impl std::iter::FusedIterator for BindingIdWithConstraintsIterator<'_, '_> {} +impl std::iter::FusedIterator for BindingIdWithConstraintsIterator<'_> {} #[derive(Debug)] pub(super) struct ConstraintIdIterator<'a> { @@ -565,20 +547,13 @@ impl Iterator for ConstraintIdIterator<'_> { impl std::iter::FusedIterator for ConstraintIdIterator<'_> {} -pub(super) struct DeclarationIdIterator<'map, 'db> { - pub(crate) all_constraints: &'map AllConstraints<'db>, - pub(crate) visibility_constraints: &'map VisibilityConstraints, +pub(super) struct DeclarationIdIterator<'map> { pub(crate) declarations_iter: DeclarationsIterator<'map>, pub(crate) visibility_constraints_iter: VisibilityConstraintsIterator<'map>, } -impl<'map, 'db> Iterator for DeclarationIdIterator<'map, 'db> { - type Item = ( - ScopedDefinitionId, - &'map AllConstraints<'db>, - &'map VisibilityConstraints, - ScopedVisibilityConstraintId, - ); +impl<'map> Iterator for DeclarationIdIterator<'map> { + type Item = (ScopedDefinitionId, ScopedVisibilityConstraintId); fn next(&mut self) -> Option { match ( @@ -588,8 +563,6 @@ impl<'map, 'db> Iterator for DeclarationIdIterator<'map, 'db> { (None, None) => None, (Some(declaration), Some(visibility_constraints_id)) => Some(( ScopedDefinitionId::from_u32(declaration), - self.all_constraints, - self.visibility_constraints, *visibility_constraints_id, )), // SAFETY: see above. @@ -598,22 +571,17 @@ impl<'map, 'db> Iterator for DeclarationIdIterator<'map, 'db> { } } -impl std::iter::FusedIterator for DeclarationIdIterator<'_, '_> {} +impl std::iter::FusedIterator for DeclarationIdIterator<'_> {} #[cfg(test)] mod tests { use super::*; #[track_caller] - fn assert_bindings( - constraints: &AllConstraints<'_>, - visibility_constraints: &VisibilityConstraints, - symbol: &SymbolState, - expected: &[&str], - ) { + fn assert_bindings(symbol: &SymbolState, expected: &[&str]) { let actual = symbol .bindings() - .iter(constraints, visibility_constraints) + .iter() .map(|def_id_with_constraints| { let def_id = def_id_with_constraints.definition; let def = if def_id == ScopedDefinitionId::UNBOUND { @@ -634,16 +602,11 @@ mod tests { } #[track_caller] - pub(crate) fn assert_declarations( - constraints: &AllConstraints<'_>, - visibility_constraints: &VisibilityConstraints, - symbol: &SymbolState, - expected: &[&str], - ) { + pub(crate) fn assert_declarations(symbol: &SymbolState, expected: &[&str]) { let actual = symbol .declarations() - .iter(constraints, visibility_constraints) - .map(|(def_id, _, _, _)| { + .iter() + .map(|(def_id, _)| { if def_id == ScopedDefinitionId::UNBOUND { "undeclared".into() } else { @@ -656,37 +619,30 @@ mod tests { #[test] fn unbound() { - let constraints = AllConstraints::new(); - let visibility_constraints = VisibilityConstraints::new(); let sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); - assert_bindings(&constraints, &visibility_constraints, &sym, &["unbound<>"]); + assert_bindings(&sym, &["unbound<>"]); } #[test] fn with() { - let constraints = AllConstraints::new(); - let visibility_constraints = VisibilityConstraints::new(); let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym.record_binding(ScopedDefinitionId::from_u32(1)); - assert_bindings(&constraints, &visibility_constraints, &sym, &["1<>"]); + assert_bindings(&sym, &["1<>"]); } #[test] fn record_constraint() { - let constraints = AllConstraints::new(); - let visibility_constraints = VisibilityConstraints::new(); let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym.record_binding(ScopedDefinitionId::from_u32(1)); sym.record_constraint(ScopedConstraintId::from_u32(0)); - assert_bindings(&constraints, &visibility_constraints, &sym, &["1<0>"]); + assert_bindings(&sym, &["1<0>"]); } #[test] fn merge() { - let constraints = AllConstraints::new(); let mut visibility_constraints = VisibilityConstraints::new(); // merging the same definition with the same constraint keeps the constraint @@ -700,7 +656,7 @@ mod tests { sym1a.merge(sym1b, &mut visibility_constraints); let mut sym1 = sym1a; - assert_bindings(&constraints, &visibility_constraints, &sym1, &["1<0>"]); + assert_bindings(&sym1, &["1<0>"]); // merging the same definition with differing constraints drops all constraints let mut sym2a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); @@ -713,7 +669,7 @@ mod tests { sym2a.merge(sym1b, &mut visibility_constraints); let sym2 = sym2a; - assert_bindings(&constraints, &visibility_constraints, &sym2, &["2<>"]); + assert_bindings(&sym2, &["2<>"]); // merging a constrained definition with unbound keeps both let mut sym3a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); @@ -724,57 +680,40 @@ mod tests { sym3a.merge(sym2b, &mut visibility_constraints); let sym3 = sym3a; - assert_bindings( - &constraints, - &visibility_constraints, - &sym3, - &["unbound<>", "3<3>"], - ); + assert_bindings(&sym3, &["unbound<>", "3<3>"]); // merging different definitions keeps them each with their existing constraints sym1.merge(sym3, &mut visibility_constraints); let sym = sym1; - assert_bindings( - &constraints, - &visibility_constraints, - &sym, - &["unbound<>", "1<0>", "3<3>"], - ); + assert_bindings(&sym, &["unbound<>", "1<0>", "3<3>"]); } #[test] fn no_declaration() { - let constraints = AllConstraints::new(); - let visibility_constraints = VisibilityConstraints::new(); let sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); - assert_declarations(&constraints, &visibility_constraints, &sym, &["undeclared"]); + assert_declarations(&sym, &["undeclared"]); } #[test] fn record_declaration() { - let constraints = AllConstraints::new(); - let visibility_constraints = VisibilityConstraints::new(); let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym.record_declaration(ScopedDefinitionId::from_u32(1)); - assert_declarations(&constraints, &visibility_constraints, &sym, &["1"]); + assert_declarations(&sym, &["1"]); } #[test] fn record_declaration_override() { - let constraints = AllConstraints::new(); - let visibility_constraints = VisibilityConstraints::new(); let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym.record_declaration(ScopedDefinitionId::from_u32(1)); sym.record_declaration(ScopedDefinitionId::from_u32(2)); - assert_declarations(&constraints, &visibility_constraints, &sym, &["2"]); + assert_declarations(&sym, &["2"]); } #[test] fn record_declaration_merge() { - let constraints = AllConstraints::new(); let mut visibility_constraints = VisibilityConstraints::new(); let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym.record_declaration(ScopedDefinitionId::from_u32(1)); @@ -784,12 +723,11 @@ mod tests { sym.merge(sym2, &mut visibility_constraints); - assert_declarations(&constraints, &visibility_constraints, &sym, &["1", "2"]); + assert_declarations(&sym, &["1", "2"]); } #[test] fn record_declaration_merge_partial_undeclared() { - let constraints = AllConstraints::new(); let mut visibility_constraints = VisibilityConstraints::new(); let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym.record_declaration(ScopedDefinitionId::from_u32(1)); @@ -798,11 +736,6 @@ mod tests { sym.merge(sym2, &mut visibility_constraints); - assert_declarations( - &constraints, - &visibility_constraints, - &sym, - &["undeclared", "1"], - ); + assert_declarations(&sym, &["undeclared", "1"]); } } From 91fa462fba4186611b03bfff67df3010180147f4 Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 19 Dec 2024 13:40:37 +0100 Subject: [PATCH 42/65] scope_start_visibility --- .../src/semantic_index/use_def.rs | 34 +++++++++++-------- .../semantic_index/use_def/symbol_state.rs | 16 +++++---- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index 92086db2b9a4b..a22b5e7183e21 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -442,7 +442,7 @@ impl std::iter::FusedIterator for DeclarationsIterator<'_, '_> {} #[derive(Clone, Debug)] pub(super) struct FlowSnapshot { symbol_states: IndexVec, - unbound_visibility: ScopedVisibilityConstraintId, + scope_start_visibility: ScopedVisibilityConstraintId, } #[derive(Debug)] @@ -457,8 +457,10 @@ pub(super) struct UseDefMapBuilder<'db> { visibility_constraints: VisibilityConstraints, /// A constraint which describes the visibility of the unbound/undeclared state, i.e. - /// whether or not the start of the scope is visible. - unbound_visibility: ScopedVisibilityConstraintId, + /// whether or not the start of the scope is visible. This is important for cases like + /// `if True: x = 1; use(x)` where we need to hide the implicit "x = unbound" binding + /// in the "else" branch. + scope_start_visibility: ScopedVisibilityConstraintId, /// Live bindings at each so-far-recorded use. bindings_by_use: IndexVec, @@ -476,7 +478,7 @@ impl<'db> UseDefMapBuilder<'db> { all_definitions: IndexVec::from_iter([None]), all_constraints: IndexVec::new(), visibility_constraints: VisibilityConstraints::new(), - unbound_visibility: ScopedVisibilityConstraintId::ALWAYS_TRUE, + scope_start_visibility: ScopedVisibilityConstraintId::ALWAYS_TRUE, bindings_by_use: IndexVec::new(), definitions_by_definition: FxHashMap::default(), symbol_states: IndexVec::new(), @@ -486,7 +488,7 @@ impl<'db> UseDefMapBuilder<'db> { pub(super) fn add_symbol(&mut self, symbol: ScopedSymbolId) { let new_symbol = self .symbol_states - .push(SymbolState::undefined(self.unbound_visibility)); + .push(SymbolState::undefined(self.scope_start_visibility)); debug_assert_eq!(symbol, new_symbol); } @@ -531,9 +533,9 @@ impl<'db> UseDefMapBuilder<'db> { state.record_visibility_constraint(&mut self.visibility_constraints, constraint); } - self.unbound_visibility = self + self.scope_start_visibility = self .visibility_constraints - .add_and_constraint(self.unbound_visibility, constraint); + .add_and_constraint(self.scope_start_visibility, constraint); } pub(super) fn record_visibility_constraint( @@ -549,7 +551,7 @@ impl<'db> UseDefMapBuilder<'db> { let num_symbols = self.symbol_states.len(); debug_assert!(num_symbols >= snapshot.symbol_states.len()); - self.unbound_visibility = snapshot.unbound_visibility; + self.scope_start_visibility = snapshot.scope_start_visibility; let mut snapshot_definitions_iter = snapshot.symbol_states.into_iter(); for current in &mut self.symbol_states { @@ -600,7 +602,7 @@ impl<'db> UseDefMapBuilder<'db> { pub(super) fn snapshot(&self) -> FlowSnapshot { FlowSnapshot { symbol_states: self.symbol_states.clone(), - unbound_visibility: self.unbound_visibility, + scope_start_visibility: self.scope_start_visibility, } } @@ -614,13 +616,15 @@ impl<'db> UseDefMapBuilder<'db> { // Restore the current visible-definitions state to the given snapshot. self.symbol_states = snapshot.symbol_states; - self.unbound_visibility = snapshot.unbound_visibility; + self.scope_start_visibility = snapshot.scope_start_visibility; // 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 // snapshot, the correct state to fill them in with is "undefined". - self.symbol_states - .resize(num_symbols, SymbolState::undefined(self.unbound_visibility)); + self.symbol_states.resize( + num_symbols, + SymbolState::undefined(self.scope_start_visibility), + ); } /// Merge the given snapshot into the current state, reflecting that we might have taken either @@ -638,16 +642,16 @@ impl<'db> UseDefMapBuilder<'db> { current.merge(snapshot, &mut self.visibility_constraints); } else { current.merge( - SymbolState::undefined(snapshot.unbound_visibility), + SymbolState::undefined(snapshot.scope_start_visibility), &mut self.visibility_constraints, ); // Symbol not present in snapshot, so it's unbound/undeclared from that path. } } - self.unbound_visibility = self + self.scope_start_visibility = self .visibility_constraints - .add_or_constraint(self.unbound_visibility, snapshot.unbound_visibility); + .add_or_constraint(self.scope_start_visibility, snapshot.scope_start_visibility); } pub(super) fn finish(mut self) -> UseDefMap<'db> { diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs index cf046e542b3da..500f8828d8fd8 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs @@ -121,11 +121,11 @@ pub(super) struct SymbolDeclarations { } impl SymbolDeclarations { - fn undeclared(undeclared_visibility: ScopedVisibilityConstraintId) -> Self { + fn undeclared(scope_start_visibility: ScopedVisibilityConstraintId) -> Self { Self { live_declarations: Declarations::with(0), visibility_constraints: VisibilityConstraintPerDeclaration::from_iter([ - undeclared_visibility, + scope_start_visibility, ]), } } @@ -176,11 +176,13 @@ pub(super) struct SymbolBindings { } impl SymbolBindings { - fn unbound(unbound_visibility: ScopedVisibilityConstraintId) -> Self { + fn unbound(scope_start_visibility: ScopedVisibilityConstraintId) -> Self { Self { live_bindings: Bindings::with(0), constraints: ConstraintsPerBinding::from_iter([Constraints::default()]), - visibility_constraints: VisibilityConstraintPerBinding::from_iter([unbound_visibility]), + visibility_constraints: VisibilityConstraintPerBinding::from_iter([ + scope_start_visibility, + ]), } } @@ -233,10 +235,10 @@ pub(super) struct SymbolState { impl SymbolState { /// Return a new [`SymbolState`] representing an unbound, undeclared symbol. - pub(super) fn undefined(unbound_visibility: ScopedVisibilityConstraintId) -> Self { + pub(super) fn undefined(scope_start_visibility: ScopedVisibilityConstraintId) -> Self { Self { - declarations: SymbolDeclarations::undeclared(unbound_visibility), - bindings: SymbolBindings::unbound(unbound_visibility), + declarations: SymbolDeclarations::undeclared(scope_start_visibility), + bindings: SymbolBindings::unbound(scope_start_visibility), } } From b37f095f6dc052e25c0cd1313e284a300db2df68 Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 19 Dec 2024 14:02:01 +0100 Subject: [PATCH 43/65] Rename to simplify_visibility_constraints --- .../src/semantic_index/builder.rs | 14 ++++++------ .../src/semantic_index/use_def.rs | 22 +++++++++++++++++-- .../semantic_index/use_def/symbol_state.rs | 2 +- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index/builder.rs b/crates/red_knot_python_semantic/src/semantic_index/builder.rs index 55e11063b7674..c89c2f05c8e91 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder.rs @@ -348,9 +348,9 @@ impl<'db> SemanticIndexBuilder<'db> { .record_visibility_constraint(VisibilityConstraint::VisibleIf(constraint)) } - fn reset_visibility_constraints(&mut self, snapshot: FlowSnapshot) { + fn simplify_visibility_constraints(&mut self, snapshot: FlowSnapshot) { self.current_use_def_map_mut() - .reset_visibility_constraints(snapshot); + .simplify_visibility_constraints(snapshot); } fn record_negated_visibility_constraint( @@ -916,7 +916,7 @@ where self.flow_merge(post_clause_state); } - self.reset_visibility_constraints(pre_if); + self.simplify_visibility_constraints(pre_if); } ast::Stmt::While(ast::StmtWhile { test, @@ -960,7 +960,7 @@ where self.flow_merge(break_state); } - self.reset_visibility_constraints(pre_loop); + self.simplify_visibility_constraints(pre_loop); } ast::Stmt::With(ast::StmtWith { items, @@ -1092,7 +1092,7 @@ where self.flow_merge(post_clause_state); } - self.reset_visibility_constraints(after_subject); + self.simplify_visibility_constraints(after_subject); } ast::Stmt::Try(ast::StmtTry { body, @@ -1354,7 +1354,7 @@ where self.visit_expr(orelse); self.record_negated_visibility_constraint(visibility_constraint); self.flow_merge(post_body); - self.reset_visibility_constraints(pre_if); + self.simplify_visibility_constraints(pre_if); } ast::Expr::ListComp( list_comprehension @ ast::ExprListComp { @@ -1459,7 +1459,7 @@ where self.flow_merge(snapshot); } - self.reset_visibility_constraints(pre_op); + self.simplify_visibility_constraints(pre_op); } _ => { walk_expr(self, expr); diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index a22b5e7183e21..80ffef90dd966 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -547,7 +547,25 @@ impl<'db> UseDefMapBuilder<'db> { new_constraint_id } - pub(super) fn reset_visibility_constraints(&mut self, snapshot: FlowSnapshot) { + /// This method resets the visibility constraints for all symbols to a previous state + /// *if* there have been no new declarations or bindings since then. Consider the + /// following example: + /// ```py + /// x = 0 + /// y = 0 + /// if test_a: + /// y = 1 + /// elif test_b: + /// y = 2 + /// elif test_c: + /// y = 3 + /// + /// # RESET + /// ``` + /// We build a complex visibility constraint for the `y = 0` binding. We build the same + /// 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) { let num_symbols = self.symbol_states.len(); debug_assert!(num_symbols >= snapshot.symbol_states.len()); @@ -556,7 +574,7 @@ impl<'db> UseDefMapBuilder<'db> { let mut snapshot_definitions_iter = snapshot.symbol_states.into_iter(); for current in &mut self.symbol_states { if let Some(snapshot) = snapshot_definitions_iter.next() { - current.reset_visibility_constraints(snapshot); + current.simplify_visibility_constraints(snapshot); } else { // Symbol not present in snapshot, keep visibility constraints } diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs index 500f8828d8fd8..e2c93f2db71c9 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs @@ -265,7 +265,7 @@ impl SymbolState { .record_visibility_constraint(visibility_constraints, constraint); } - pub(super) fn reset_visibility_constraints(&mut self, snapshot_state: SymbolState) { + pub(super) fn simplify_visibility_constraints(&mut self, snapshot_state: SymbolState) { if self.bindings.live_bindings == snapshot_state.bindings.live_bindings { self.bindings.visibility_constraints = snapshot_state.bindings.visibility_constraints; } From 090563e4a67d83e7bd8bf2758632e7744d640003 Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 19 Dec 2024 14:08:26 +0100 Subject: [PATCH 44/65] Fix comment --- crates/red_knot_python_semantic/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index a7f5bcd225889..7e923d0f38817 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -91,7 +91,7 @@ fn symbol_by_id<'db>(db: &'db dyn Db, scope: ScopeId<'db>, symbol: ScopedSymbolI match inferred_ty { // Symbol is possibly undeclared and definitely unbound Symbol::Unbound => Symbol::Type(declared_ty, Boundness::Bound), - // Symbol is possibly undeclared and possibly bound + // Symbol is possibly undeclared and definitely bound inferred @ Symbol::Type(_, Boundness::Bound) => inferred, // Symbol is possibly undeclared and possibly unbound Symbol::Type(inferred_ty, Boundness::PossiblyUnbound) => Symbol::Type( From 47bb571b4d6473bdae6211874f715f3c37398fe5 Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 19 Dec 2024 14:09:42 +0100 Subject: [PATCH 45/65] Update comment --- crates/red_knot_python_semantic/src/types.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 7e923d0f38817..f65de76a2b44d 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -104,7 +104,8 @@ fn symbol_by_id<'db>(db: &'db dyn Db, scope: ScopeId<'db>, symbol: ScopedSymbolI Ok(Symbol::Unbound) => inferred_ty, // Symbol is possibly undeclared Err((declared_ty, _)) => { - // TODO: conflicting declarations error + // Intentionally ignore conflicting declared types; that's not our problem, + // it's the problem of the module we are importing from. declared_ty.into() } } From c9eb782d5741f760f2b738e4da0caa54c96e7261 Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 19 Dec 2024 15:11:04 +0100 Subject: [PATCH 46/65] Handle ambiguous visibility, fix while-loop inference --- .../mdtest/statically_known_branches.md | 14 ++++++++ .../src/semantic_index/builder.rs | 19 ++++++++--- crates/red_knot_python_semantic/src/types.rs | 33 +++++++++---------- .../src/visibility_constraints.rs | 2 ++ 4 files changed, 47 insertions(+), 21 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md b/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md index ea3c1f50d12bc..11e97d17480d0 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md +++ b/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md @@ -943,6 +943,20 @@ else: reveal_type(x) # revealed: Literal[2] ``` +#### `while False` with `break` + +```py +x = 1 +while False: + x = 2 + break + x = 3 +else: + x = 4 + +reveal_type(x) # revealed: Literal[4] +``` + #### `while True` ```py diff --git a/crates/red_knot_python_semantic/src/semantic_index/builder.rs b/crates/red_knot_python_semantic/src/semantic_index/builder.rs index c89c2f05c8e91..5696ec11449e5 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder.rs @@ -348,6 +348,11 @@ impl<'db> SemanticIndexBuilder<'db> { .record_visibility_constraint(VisibilityConstraint::VisibleIf(constraint)) } + fn record_ambiguous_visibility(&mut self) -> ScopedVisibilityConstraintId { + self.current_use_def_map_mut() + .record_visibility_constraint(VisibilityConstraint::Ambiguous) + } + fn simplify_visibility_constraints(&mut self, snapshot: FlowSnapshot) { self.current_use_def_map_mut() .simplify_visibility_constraints(snapshot); @@ -939,25 +944,27 @@ where self.visit_body(body); self.set_inside_loop(outer_loop_state); + let vis_constraint_id = self.record_visibility_constraint(constraint_id); + // Get the break states from the body of this loop, and restore the saved outer // ones. let break_states = std::mem::replace(&mut self.loop_break_states, saved_break_states); - let vis_constraint_id = self.record_visibility_constraint(constraint_id); - // We may execute the `else` clause without ever executing the body, so merge in // the pre-loop state before visiting `else`. self.flow_merge(pre_loop.clone()); self.record_negated_constraint(constraint); self.visit_body(orelse); - self.record_negated_visibility_constraint(vis_constraint_id); // Breaking out of a while loop bypasses the `else` clause, so merge in the break // states after visiting `else`. for break_state in break_states { - self.flow_merge(break_state); + let snapshot = self.flow_snapshot(); + self.flow_restore(break_state); + self.record_visibility_constraint(constraint_id); + self.flow_merge(snapshot); } self.simplify_visibility_constraints(pre_loop); @@ -1001,6 +1008,8 @@ where self.add_standalone_expression(iter); self.visit_expr(iter); + self.record_ambiguous_visibility(); + let pre_loop = self.flow_snapshot(); let saved_break_states = std::mem::take(&mut self.loop_break_states); @@ -1102,6 +1111,8 @@ where is_star, range: _, }) => { + self.record_ambiguous_visibility(); + // Save the state prior to visiting any of the `try` block. // // Potentially none of the `try` block could have been executed prior to executing diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index f65de76a2b44d..2fa5da7f178dc 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -267,7 +267,7 @@ fn bindings_ty<'db>( db: &'db dyn Db, bindings_with_constraints: BindingWithConstraintsIterator<'_, 'db>, ) -> Symbol<'db> { - let mut is_unbound_visible = false; + let mut unbound_visibility = Truthiness::AlwaysFalse; let mut types = vec![]; for BindingWithConstraints { @@ -282,10 +282,7 @@ fn bindings_ty<'db>( visibility_constraints.evaluate(db, all_constraints, visibility_constraint); let Some(binding) = binding else { - if !static_visibility.is_always_false() { - // TODO: also distinguish between always-true and ambiguous? - is_unbound_visible = true; - } + unbound_visibility = static_visibility; continue; }; @@ -316,10 +313,12 @@ fn bindings_ty<'db>( let mut types = types.into_iter(); if let Some(first) = types.next() { - let boundness = if is_unbound_visible { - Boundness::PossiblyUnbound - } else { - Boundness::Bound + let boundness = match unbound_visibility { + Truthiness::AlwaysTrue => { + unreachable!("If we have at least one binding, the scope-start should not be definitely visible") + } + Truthiness::AlwaysFalse => Boundness::Bound, + Truthiness::Ambiguous => Boundness::PossiblyUnbound, }; if let Some(second) = types.next() { @@ -355,7 +354,7 @@ fn declarations_ty<'db>( db: &'db dyn Db, declarations: DeclarationsIterator<'_, 'db>, ) -> DeclaredTypeResult<'db> { - let mut is_unbound_visible = false; + let mut unbound_visibility = Truthiness::AlwaysFalse; let mut types = vec![]; for (declaration, all_constraints, visibility_constraints, visibility_constraint) in @@ -365,9 +364,7 @@ fn declarations_ty<'db>( visibility_constraints.evaluate(db, all_constraints, visibility_constraint); let Some(declaration) = declaration else { - if !static_visibility.is_always_false() { - is_unbound_visible = true; - } + unbound_visibility = static_visibility; continue; }; @@ -396,10 +393,12 @@ fn declarations_ty<'db>( first }; if conflicting.is_empty() { - let boundness = if is_unbound_visible { - Boundness::PossiblyUnbound - } else { - Boundness::Bound + let boundness = match unbound_visibility { + Truthiness::AlwaysTrue => { + unreachable!("If we have at least one declaration, the scope-start should not be definitely visible") + } + Truthiness::AlwaysFalse => Boundness::Bound, + Truthiness::Ambiguous => Boundness::PossiblyUnbound, }; Ok(Symbol::Type(declared_ty, boundness)) diff --git a/crates/red_knot_python_semantic/src/visibility_constraints.rs b/crates/red_knot_python_semantic/src/visibility_constraints.rs index 2469b67c65964..d7c6a8fb08c74 100644 --- a/crates/red_knot_python_semantic/src/visibility_constraints.rs +++ b/crates/red_knot_python_semantic/src/visibility_constraints.rs @@ -14,6 +14,7 @@ const MAX_RECURSION_DEPTH: usize = 10; #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum VisibilityConstraint { AlwaysTrue, + Ambiguous, VisibleIf(ScopedConstraintId), VisibleIfNot(ScopedVisibilityConstraintId), KleeneAnd(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), @@ -90,6 +91,7 @@ impl VisibilityConstraints { let visibility_constraint = &self.constraints[id]; match visibility_constraint { VisibilityConstraint::AlwaysTrue => Truthiness::AlwaysTrue, + VisibilityConstraint::Ambiguous => Truthiness::Ambiguous, VisibilityConstraint::VisibleIf(single) => { Self::analyze_single(db, &constraints[*single]) } From 22e345abc98a86aa00eb04c415598179cbe5337f Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 19 Dec 2024 15:15:11 +0100 Subject: [PATCH 47/65] Clippy --- .../src/semantic_index/use_def/symbol_state.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs index e2c93f2db71c9..55c14f2eb5afd 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs @@ -151,7 +151,7 @@ impl SymbolDeclarations { } /// Return an iterator over live declarations for this symbol. - pub(super) fn iter<'map, 'db>(&'map self) -> DeclarationIdIterator<'map> { + pub(super) fn iter(&self) -> DeclarationIdIterator { DeclarationIdIterator { declarations_iter: self.live_declarations.iter(), visibility_constraints_iter: self.visibility_constraints.iter(), @@ -218,7 +218,7 @@ impl SymbolBindings { } /// Iterate over currently live bindings for this symbol - pub(super) fn iter<'map>(&'map self) -> BindingIdWithConstraintsIterator<'map> { + pub(super) fn iter(&self) -> BindingIdWithConstraintsIterator { BindingIdWithConstraintsIterator { definitions: self.live_bindings.iter(), constraints: self.constraints.iter(), @@ -507,7 +507,7 @@ pub(super) struct BindingIdWithConstraintsIterator<'map> { visibility_constraints_iter: VisibilityConstraintsIterator<'map>, } -impl<'map, 'db> Iterator for BindingIdWithConstraintsIterator<'map> { +impl<'map> Iterator for BindingIdWithConstraintsIterator<'map> { type Item = BindingIdWithConstraints<'map>; fn next(&mut self) -> Option { @@ -554,7 +554,7 @@ pub(super) struct DeclarationIdIterator<'map> { pub(crate) visibility_constraints_iter: VisibilityConstraintsIterator<'map>, } -impl<'map> Iterator for DeclarationIdIterator<'map> { +impl Iterator for DeclarationIdIterator<'_> { type Item = (ScopedDefinitionId, ScopedVisibilityConstraintId); fn next(&mut self) -> Option { From a4d805f86c7d2a838a6015cec8770dc2a1da3347 Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 19 Dec 2024 15:55:18 +0100 Subject: [PATCH 48/65] Fix tests after rebase --- .../red_knot_python_semantic/resources/mdtest/sys_platform.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/sys_platform.md b/crates/red_knot_python_semantic/resources/mdtest/sys_platform.md index 0c5d6944c9a8e..637230efaad5d 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/sys_platform.md +++ b/crates/red_knot_python_semantic/resources/mdtest/sys_platform.md @@ -66,6 +66,6 @@ It is [recommended](https://docs.python.org/3/library/sys.html#sys.platform) to ```py import sys -reveal_type(sys.platform.startswith("freebsd")) # revealed: @Todo(call todo) -reveal_type(sys.platform.startswith("linux")) # revealed: @Todo(call todo) +reveal_type(sys.platform.startswith("freebsd")) # revealed: @Todo(instance attributes) +reveal_type(sys.platform.startswith("linux")) # revealed: @Todo(instance attributes) ``` From f9f461540cda345a05257c2b9da1009aedc7c2bb Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 19 Dec 2024 15:59:50 +0100 Subject: [PATCH 49/65] Remove _iter suffix --- .../semantic_index/use_def/symbol_state.rs | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs index 55c14f2eb5afd..87be32f3b08d5 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs @@ -153,8 +153,8 @@ impl SymbolDeclarations { /// Return an iterator over live declarations for this symbol. pub(super) fn iter(&self) -> DeclarationIdIterator { DeclarationIdIterator { - declarations_iter: self.live_declarations.iter(), - visibility_constraints_iter: self.visibility_constraints.iter(), + declarations: self.live_declarations.iter(), + visibility_constraints: self.visibility_constraints.iter(), } } } @@ -222,7 +222,7 @@ impl SymbolBindings { BindingIdWithConstraintsIterator { definitions: self.live_bindings.iter(), constraints: self.constraints.iter(), - visibility_constraints_iter: self.visibility_constraints.iter(), + visibility_constraints: self.visibility_constraints.iter(), } } } @@ -435,14 +435,18 @@ impl SymbolState { let mut opt_a_decl: Option = a_decls_iter.next(); let mut opt_b_decl: Option = b_decls_iter.next(); - let push = - |decl, conditions_iter: &mut VisibilityConstraintsIntoIterator, merged: &mut Self| { - merged.declarations.live_declarations.insert(decl); - let conditions = conditions_iter - .next() - .expect("declarations and visibility_constraints length mismatch"); - merged.declarations.visibility_constraints.push(conditions); - }; + let push = |decl, + vis_constraints_iter: &mut VisibilityConstraintsIntoIterator, + merged: &mut Self| { + merged.declarations.live_declarations.insert(decl); + let vis_constraints = vis_constraints_iter + .next() + .expect("declarations and visibility_constraints length mismatch"); + merged + .declarations + .visibility_constraints + .push(vis_constraints); + }; loop { match (opt_a_decl, opt_b_decl) { @@ -504,7 +508,7 @@ pub(super) struct BindingIdWithConstraints<'map> { pub(super) struct BindingIdWithConstraintsIterator<'map> { definitions: BindingsIterator<'map>, constraints: ConstraintsIterator<'map>, - visibility_constraints_iter: VisibilityConstraintsIterator<'map>, + visibility_constraints: VisibilityConstraintsIterator<'map>, } impl<'map> Iterator for BindingIdWithConstraintsIterator<'map> { @@ -514,7 +518,7 @@ impl<'map> Iterator for BindingIdWithConstraintsIterator<'map> { match ( self.definitions.next(), self.constraints.next(), - self.visibility_constraints_iter.next(), + self.visibility_constraints.next(), ) { (None, None, None) => None, (Some(def), Some(constraints), Some(visibility_constraint_id)) => { @@ -550,18 +554,15 @@ impl Iterator for ConstraintIdIterator<'_> { impl std::iter::FusedIterator for ConstraintIdIterator<'_> {} pub(super) struct DeclarationIdIterator<'map> { - pub(crate) declarations_iter: DeclarationsIterator<'map>, - pub(crate) visibility_constraints_iter: VisibilityConstraintsIterator<'map>, + pub(crate) declarations: DeclarationsIterator<'map>, + pub(crate) visibility_constraints: VisibilityConstraintsIterator<'map>, } impl Iterator for DeclarationIdIterator<'_> { type Item = (ScopedDefinitionId, ScopedVisibilityConstraintId); fn next(&mut self) -> Option { - match ( - self.declarations_iter.next(), - self.visibility_constraints_iter.next(), - ) { + match (self.declarations.next(), self.visibility_constraints.next()) { (None, None) => None, (Some(declaration), Some(visibility_constraints_id)) => Some(( ScopedDefinitionId::from_u32(declaration), From 43537309130604f0d75be9b0157a07858e888ae3 Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 19 Dec 2024 16:14:39 +0100 Subject: [PATCH 50/65] Store Expression<'db> inside VisibilityConstraint --- .../src/semantic_index.rs | 4 +- .../src/semantic_index/builder.rs | 82 +++++++++---------- .../src/semantic_index/use_def.rs | 33 +++----- crates/red_knot_python_semantic/src/types.rs | 11 +-- .../src/visibility_constraints.rs | 46 +++++------ 5 files changed, 74 insertions(+), 102 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index.rs b/crates/red_knot_python_semantic/src/semantic_index.rs index b597717bb9254..14bb11030d962 100644 --- a/crates/red_knot_python_semantic/src/semantic_index.rs +++ b/crates/red_knot_python_semantic/src/semantic_index.rs @@ -29,8 +29,8 @@ pub mod symbol; mod use_def; pub(crate) use self::use_def::{ - AllConstraints, BindingWithConstraints, BindingWithConstraintsIterator, DeclarationsIterator, - ScopedConstraintId, ScopedVisibilityConstraintId, + BindingWithConstraints, BindingWithConstraintsIterator, DeclarationsIterator, + ScopedVisibilityConstraintId, }; type SymbolMap = hashbrown::HashMap; diff --git a/crates/red_knot_python_semantic/src/semantic_index/builder.rs b/crates/red_knot_python_semantic/src/semantic_index/builder.rs index 5696ec11449e5..ef42aed58b665 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder.rs @@ -26,7 +26,7 @@ use crate::semantic_index::symbol::{ SymbolTableBuilder, }; use crate::semantic_index::use_def::{ - FlowSnapshot, ScopedConstraintId, ScopedVisibilityConstraintId, UseDefMapBuilder, + FlowSnapshot, ScopedVisibilityConstraintId, UseDefMapBuilder, }; use crate::semantic_index::SemanticIndex; use crate::unpack::Unpack; @@ -283,13 +283,10 @@ impl<'db> SemanticIndexBuilder<'db> { definition } - fn record_expression_constraint( - &mut self, - constraint_node: &ast::Expr, - ) -> (ScopedConstraintId, Constraint<'db>) { + fn record_expression_constraint(&mut self, constraint_node: &ast::Expr) -> Constraint<'db> { let constraint = self.build_constraint(constraint_node); - let constraint_id = self.record_constraint(constraint); - (constraint_id, constraint) + self.record_constraint(constraint); + constraint } fn build_constraint(&mut self, constraint_node: &Expr) -> Constraint<'db> { @@ -300,36 +297,33 @@ impl<'db> SemanticIndexBuilder<'db> { } } - fn add_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { - self.current_use_def_map_mut().add_constraint(constraint) + fn add_constraint(&mut self, constraint: Constraint<'db>) -> Constraint<'db> { + self.current_use_def_map_mut().add_constraint(constraint); + constraint } - fn add_negated_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { + fn add_negated_constraint(&mut self, constraint: Constraint<'db>) -> Constraint<'db> { let negated = Constraint { node: constraint.node, is_positive: false, }; - self.current_use_def_map_mut().add_constraint(negated) - } - - fn record_constraint_id(&mut self, constraint: ScopedConstraintId) { - self.current_use_def_map_mut() - .record_constraint_id(constraint); + self.current_use_def_map_mut().add_constraint(negated); + negated } - fn record_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { - self.current_use_def_map_mut().record_constraint(constraint) + fn record_constraint(&mut self, constraint: Constraint<'db>) { + self.current_use_def_map_mut().record_constraint(constraint); } - fn record_negated_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { - let constraint_id = self.add_negated_constraint(constraint); - self.record_constraint_id(constraint_id); - constraint_id + fn record_negated_constraint(&mut self, constraint: Constraint<'db>) -> Constraint<'db> { + let negated = self.add_negated_constraint(constraint); + self.record_constraint(negated); + negated } fn add_visibility_constraint( &mut self, - constraint: VisibilityConstraint, + constraint: VisibilityConstraint<'db>, ) -> ScopedVisibilityConstraintId { self.current_use_def_map_mut() .add_visibility_constraint(constraint) @@ -342,7 +336,7 @@ impl<'db> SemanticIndexBuilder<'db> { fn record_visibility_constraint( &mut self, - constraint: ScopedConstraintId, + constraint: Constraint<'db>, ) -> ScopedVisibilityConstraintId { self.current_use_def_map_mut() .record_visibility_constraint(VisibilityConstraint::VisibleIf(constraint)) @@ -388,7 +382,7 @@ impl<'db> SemanticIndexBuilder<'db> { subject: Expression<'db>, pattern: &ast::Pattern, guard: Option<&ast::Expr>, - ) -> ScopedConstraintId { + ) -> Constraint<'db> { let guard = guard.map(|guard| self.add_standalone_expression(guard)); let kind = match pattern { @@ -410,11 +404,12 @@ impl<'db> SemanticIndexBuilder<'db> { kind, countme::Count::default(), ); - self.current_use_def_map_mut() - .record_constraint(Constraint { - node: ConstraintNode::Pattern(pattern_constraint), - is_positive: true, - }) + let constraint = Constraint { + node: ConstraintNode::Pattern(pattern_constraint), + is_positive: true, + }; + self.current_use_def_map_mut().record_constraint(constraint); + constraint } /// Record an expression that needs to be a Salsa ingredient, because we need to infer its type @@ -863,11 +858,11 @@ where ast::Stmt::If(node) => { self.visit_expr(&node.test); let pre_if = self.flow_snapshot(); - let (constraint_id, constraint) = self.record_expression_constraint(&node.test); + let constraint = self.record_expression_constraint(&node.test); let mut constraints = vec![constraint]; self.visit_body(&node.body); - let visibility_constraint_id = self.record_visibility_constraint(constraint_id); + let visibility_constraint_id = self.record_visibility_constraint(constraint); let mut vis_constraints = vec![visibility_constraint_id]; let mut post_clauses: Vec = vec![]; @@ -899,10 +894,9 @@ where let elif_constraint = if let Some(elif_test) = clause_test { self.visit_expr(elif_test); - let (constraint_id, constraint) = - self.record_expression_constraint(elif_test); + let constraint = self.record_expression_constraint(elif_test); constraints.push(constraint); - Some(constraint_id) + Some(constraint) } else { None }; @@ -932,7 +926,7 @@ where self.visit_expr(test); let pre_loop = self.flow_snapshot(); - let (constraint_id, constraint) = self.record_expression_constraint(test); + let constraint = self.record_expression_constraint(test); // Save aside any break states from an outer loop let saved_break_states = std::mem::take(&mut self.loop_break_states); @@ -944,7 +938,7 @@ where self.visit_body(body); self.set_inside_loop(outer_loop_state); - let vis_constraint_id = self.record_visibility_constraint(constraint_id); + let vis_constraint_id = self.record_visibility_constraint(constraint); // Get the break states from the body of this loop, and restore the saved outer // ones. @@ -963,7 +957,7 @@ where for break_state in break_states { let snapshot = self.flow_snapshot(); self.flow_restore(break_state); - self.record_visibility_constraint(constraint_id); + self.record_visibility_constraint(constraint); self.flow_merge(snapshot); } @@ -1355,9 +1349,9 @@ where }) => { self.visit_expr(test); let pre_if = self.flow_snapshot(); - let (constraint_id, constraint) = self.record_expression_constraint(test); + let constraint = self.record_expression_constraint(test); self.visit_expr(body); - let visibility_constraint = self.record_visibility_constraint(constraint_id); + let visibility_constraint = self.record_visibility_constraint(constraint); let post_body = self.flow_snapshot(); self.flow_restore(pre_if.clone()); @@ -1438,12 +1432,12 @@ where // anymore. if index < values.len() - 1 { let constraint = self.build_constraint(value); - let id = match op { + let constraint = match op { BoolOp::And => self.add_constraint(constraint), BoolOp::Or => self.add_negated_constraint(constraint), }; - let visibility_constraint = - self.add_visibility_constraint(VisibilityConstraint::VisibleIf(id)); + let visibility_constraint = self + .add_visibility_constraint(VisibilityConstraint::VisibleIf(constraint)); let after_expr = self.flow_snapshot(); @@ -1461,7 +1455,7 @@ where // the application of the visibility constraint until after the expression // has been evaluated, so we only push it onto the stack here. self.flow_restore(after_expr); - self.record_constraint_id(id); + self.record_constraint(constraint); visibility_constraints.push(visibility_constraint); } } diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index 80ffef90dd966..0b022615b2eae 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -250,7 +250,7 @@ pub(crate) struct UseDefMap<'db> { all_constraints: AllConstraints<'db>, /// Array of [`VisibilityConstraint`]s in this scope. - visibility_constraints: VisibilityConstraints, + visibility_constraints: VisibilityConstraints<'db>, /// [`SymbolBindings`] reaching a [`ScopedUseId`]. bindings_by_use: IndexVec, @@ -339,7 +339,6 @@ impl<'db> UseDefMap<'db> { ) -> DeclarationsIterator<'map, 'db> { DeclarationsIterator { all_definitions: &self.all_definitions, - all_constraints: &self.all_constraints, visibility_constraints: &self.visibility_constraints, inner: declarations.iter(), } @@ -357,7 +356,7 @@ enum SymbolDefinitions { pub(crate) struct BindingWithConstraintsIterator<'map, 'db> { all_definitions: &'map IndexVec>>, all_constraints: &'map AllConstraints<'db>, - all_visibility_constraints: &'map VisibilityConstraints, + all_visibility_constraints: &'map VisibilityConstraints<'db>, inner: BindingIdWithConstraintsIterator<'map>, } @@ -375,7 +374,6 @@ impl<'map, 'db> Iterator for BindingWithConstraintsIterator<'map, 'db> { all_constraints, constraint_ids: binding_id_with_constraints.constraint_ids, }, - all_constraints: self.all_constraints, visibility_constraints: self.all_visibility_constraints, visibility_constraint: binding_id_with_constraints.visibility_constraint, }) @@ -387,8 +385,7 @@ impl std::iter::FusedIterator for BindingWithConstraintsIterator<'_, '_> {} pub(crate) struct BindingWithConstraints<'map, 'db> { pub(crate) binding: Option>, pub(crate) constraints: ConstraintsIterator<'map, 'db>, - pub(crate) all_constraints: &'map AllConstraints<'db>, - pub(crate) visibility_constraints: &'map VisibilityConstraints, + pub(crate) visibility_constraints: &'map VisibilityConstraints<'db>, pub(crate) visibility_constraint: ScopedVisibilityConstraintId, } @@ -411,16 +408,14 @@ impl std::iter::FusedIterator for ConstraintsIterator<'_, '_> {} pub(crate) struct DeclarationsIterator<'map, 'db> { all_definitions: &'map IndexVec>>, - all_constraints: &'map AllConstraints<'db>, - visibility_constraints: &'map VisibilityConstraints, + visibility_constraints: &'map VisibilityConstraints<'db>, inner: DeclarationIdIterator<'map>, } impl<'map, 'db> Iterator for DeclarationsIterator<'map, 'db> { type Item = ( Option>, - &'map AllConstraints<'db>, - &'map VisibilityConstraints, + &'map VisibilityConstraints<'db>, ScopedVisibilityConstraintId, ); @@ -428,7 +423,6 @@ impl<'map, 'db> Iterator for DeclarationsIterator<'map, 'db> { self.inner.next().map(|(def_id, visibility_constraint_id)| { ( self.all_definitions[def_id], - self.all_constraints, self.visibility_constraints, visibility_constraint_id, ) @@ -454,7 +448,7 @@ pub(super) struct UseDefMapBuilder<'db> { all_constraints: AllConstraints<'db>, /// Append-only array of [`VisibilityConstraint`]. - visibility_constraints: VisibilityConstraints, + visibility_constraints: VisibilityConstraints<'db>, /// A constraint which describes the visibility of the unbound/undeclared state, i.e. /// whether or not the start of the scope is visible. This is important for cases like @@ -506,21 +500,16 @@ impl<'db> UseDefMapBuilder<'db> { self.all_constraints.push(constraint) } - pub(super) fn record_constraint_id(&mut self, constraint: ScopedConstraintId) { + pub(super) fn record_constraint(&mut self, constraint: Constraint<'db>) { + let constraint_id = self.add_constraint(constraint); for state in &mut self.symbol_states { - state.record_constraint(constraint); + state.record_constraint(constraint_id); } } - pub(super) fn record_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { - let constraint_id = self.add_constraint(constraint); - self.record_constraint_id(constraint_id); - constraint_id - } - pub(super) fn add_visibility_constraint( &mut self, - constraint: VisibilityConstraint, + constraint: VisibilityConstraint<'db>, ) -> ScopedVisibilityConstraintId { self.visibility_constraints.add(constraint) } @@ -540,7 +529,7 @@ impl<'db> UseDefMapBuilder<'db> { pub(super) fn record_visibility_constraint( &mut self, - constraint: VisibilityConstraint, + constraint: VisibilityConstraint<'db>, ) -> ScopedVisibilityConstraintId { let new_constraint_id = self.add_visibility_constraint(constraint); self.record_visibility_constraint_id(new_constraint_id); diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 2fa5da7f178dc..16376341cb553 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -273,13 +273,11 @@ fn bindings_ty<'db>( for BindingWithConstraints { binding, constraints, - all_constraints, visibility_constraints, visibility_constraint, } in bindings_with_constraints { - let static_visibility = - visibility_constraints.evaluate(db, all_constraints, visibility_constraint); + let static_visibility = visibility_constraints.evaluate(db, visibility_constraint); let Some(binding) = binding else { unbound_visibility = static_visibility; @@ -357,11 +355,8 @@ fn declarations_ty<'db>( let mut unbound_visibility = Truthiness::AlwaysFalse; let mut types = vec![]; - for (declaration, all_constraints, visibility_constraints, visibility_constraint) in - declarations - { - let static_visibility = - visibility_constraints.evaluate(db, all_constraints, visibility_constraint); + for (declaration, visibility_constraints, visibility_constraint) in declarations { + let static_visibility = visibility_constraints.evaluate(db, visibility_constraint); let Some(declaration) = declaration else { unbound_visibility = static_visibility; diff --git a/crates/red_knot_python_semantic/src/visibility_constraints.rs b/crates/red_knot_python_semantic/src/visibility_constraints.rs index d7c6a8fb08c74..66044b7ec8957 100644 --- a/crates/red_knot_python_semantic/src/visibility_constraints.rs +++ b/crates/red_knot_python_semantic/src/visibility_constraints.rs @@ -1,39 +1,41 @@ use ruff_index::IndexVec; +use crate::semantic_index::ScopedVisibilityConstraintId; use crate::semantic_index::{ ast_ids::HasScopedExpressionId, constraint::{Constraint, ConstraintNode, PatternConstraintKind}, - AllConstraints, }; -use crate::semantic_index::{ScopedConstraintId, ScopedVisibilityConstraintId}; use crate::types::{infer_expression_types, Truthiness}; use crate::Db; const MAX_RECURSION_DEPTH: usize = 10; #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) enum VisibilityConstraint { +pub(crate) enum VisibilityConstraint<'db> { AlwaysTrue, Ambiguous, - VisibleIf(ScopedConstraintId), + VisibleIf(Constraint<'db>), VisibleIfNot(ScopedVisibilityConstraintId), KleeneAnd(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), KleeneOr(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), } #[derive(Debug, PartialEq, Eq)] -pub(crate) struct VisibilityConstraints { - constraints: IndexVec, +pub(crate) struct VisibilityConstraints<'db> { + constraints: IndexVec>, } -impl VisibilityConstraints { +impl<'db> VisibilityConstraints<'db> { pub(crate) fn new() -> Self { Self { constraints: IndexVec::from_iter([VisibilityConstraint::AlwaysTrue]), } } - pub(crate) fn add(&mut self, constraint: VisibilityConstraint) -> ScopedVisibilityConstraintId { + pub(crate) fn add( + &mut self, + constraint: VisibilityConstraint<'db>, + ) -> ScopedVisibilityConstraintId { self.constraints.push(constraint) } @@ -68,19 +70,13 @@ impl VisibilityConstraints { } /// Analyze the statically known visibility for a given visibility constraint. - pub(crate) fn evaluate<'db>( - &self, - db: &'db dyn Db, - all_constraints: &AllConstraints<'db>, - id: ScopedVisibilityConstraintId, - ) -> Truthiness { - self.evaluate_impl(db, all_constraints, id, MAX_RECURSION_DEPTH) + pub(crate) fn evaluate(&self, db: &'db dyn Db, id: ScopedVisibilityConstraintId) -> Truthiness { + self.evaluate_impl(db, id, MAX_RECURSION_DEPTH) } - fn evaluate_impl<'db>( + fn evaluate_impl( &self, db: &'db dyn Db, - constraints: &AllConstraints<'db>, id: ScopedVisibilityConstraintId, max_depth: usize, ) -> Truthiness { @@ -92,20 +88,18 @@ impl VisibilityConstraints { match visibility_constraint { VisibilityConstraint::AlwaysTrue => Truthiness::AlwaysTrue, VisibilityConstraint::Ambiguous => Truthiness::Ambiguous, - VisibilityConstraint::VisibleIf(single) => { - Self::analyze_single(db, &constraints[*single]) + VisibilityConstraint::VisibleIf(constraint) => Self::analyze_single(db, constraint), + VisibilityConstraint::VisibleIfNot(negated) => { + self.evaluate_impl(db, *negated, max_depth - 1).negate() } - VisibilityConstraint::VisibleIfNot(negated) => self - .evaluate_impl(db, constraints, *negated, max_depth - 1) - .negate(), VisibilityConstraint::KleeneAnd(lhs, rhs) => { - let lhs = self.evaluate_impl(db, constraints, *lhs, max_depth - 1); + let lhs = self.evaluate_impl(db, *lhs, max_depth - 1); if lhs == Truthiness::AlwaysFalse { return Truthiness::AlwaysFalse; } - let rhs = self.evaluate_impl(db, constraints, *rhs, max_depth - 1); + let rhs = self.evaluate_impl(db, *rhs, max_depth - 1); if rhs == Truthiness::AlwaysFalse { Truthiness::AlwaysFalse @@ -116,13 +110,13 @@ impl VisibilityConstraints { } } VisibilityConstraint::KleeneOr(lhs_id, rhs_id) => { - let lhs = self.evaluate_impl(db, constraints, *lhs_id, max_depth - 1); + let lhs = self.evaluate_impl(db, *lhs_id, max_depth - 1); if lhs == Truthiness::AlwaysTrue { return Truthiness::AlwaysTrue; } - let rhs = self.evaluate_impl(db, constraints, *rhs_id, max_depth - 1); + let rhs = self.evaluate_impl(db, *rhs_id, max_depth - 1); if rhs == Truthiness::AlwaysTrue { Truthiness::AlwaysTrue From 402f2792eabea6655444d4f8f2a93de890e490d3 Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 19 Dec 2024 16:33:23 +0100 Subject: [PATCH 51/65] Use iterators instead of vec --- crates/red_knot_python_semantic/src/types.rs | 109 ++++++++++--------- 1 file changed, 57 insertions(+), 52 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 16376341cb553..53affa43b46d4 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -267,48 +267,52 @@ fn bindings_ty<'db>( db: &'db dyn Db, bindings_with_constraints: BindingWithConstraintsIterator<'_, 'db>, ) -> Symbol<'db> { - let mut unbound_visibility = Truthiness::AlwaysFalse; - let mut types = vec![]; + let mut bindings_with_constraints = bindings_with_constraints.peekable(); - for BindingWithConstraints { - binding, - constraints, + let unbound_visibility = if let Some(BindingWithConstraints { + binding: None, + constraints: _, visibility_constraints, visibility_constraint, - } in bindings_with_constraints + }) = bindings_with_constraints.peek() { - let static_visibility = visibility_constraints.evaluate(db, visibility_constraint); - - let Some(binding) = binding else { - unbound_visibility = static_visibility; - continue; - }; - - if static_visibility.is_always_false() { - continue; - } + visibility_constraints.evaluate(db, *visibility_constraint) + } else { + Truthiness::AlwaysFalse + }; - let mut constraint_tys = constraints - .filter_map(|constraint| narrowing_constraint(db, constraint, binding)) - .peekable(); - - let binding_ty = binding_ty(db, binding); - let ty = if constraint_tys.peek().is_some() { - let intersection_ty = constraint_tys - .fold( - IntersectionBuilder::new(db).add_positive(binding_ty), - IntersectionBuilder::add_positive, - ) - .build(); - intersection_ty - } else { - binding_ty - }; + let mut types = bindings_with_constraints.filter_map( + |BindingWithConstraints { + binding, + constraints, + visibility_constraints, + visibility_constraint, + }| { + let binding = binding?; + let static_visibility = visibility_constraints.evaluate(db, visibility_constraint); + + if static_visibility.is_always_false() { + return None; + } - types.push(ty); - } + let mut constraint_tys = constraints + .filter_map(|constraint| narrowing_constraint(db, constraint, binding)) + .peekable(); - let mut types = types.into_iter(); + let binding_ty = binding_ty(db, binding); + if constraint_tys.peek().is_some() { + let intersection_ty = constraint_tys + .fold( + IntersectionBuilder::new(db).add_positive(binding_ty), + IntersectionBuilder::add_positive, + ) + .build(); + Some(intersection_ty) + } else { + Some(binding_ty) + } + }, + ); if let Some(first) = types.next() { let boundness = match unbound_visibility { @@ -352,26 +356,27 @@ fn declarations_ty<'db>( db: &'db dyn Db, declarations: DeclarationsIterator<'_, 'db>, ) -> DeclaredTypeResult<'db> { - let mut unbound_visibility = Truthiness::AlwaysFalse; - let mut types = vec![]; - - for (declaration, visibility_constraints, visibility_constraint) in declarations { - let static_visibility = visibility_constraints.evaluate(db, visibility_constraint); + let mut declarations = declarations.peekable(); - let Some(declaration) = declaration else { - unbound_visibility = static_visibility; - continue; + let undeclared_visibility = + if let Some((None, visibility_constraints, visibility_constraint)) = declarations.peek() { + visibility_constraints.evaluate(db, *visibility_constraint) + } else { + Truthiness::AlwaysFalse }; - if static_visibility.is_always_false() { - continue; - } - - let ty = declaration_ty(db, declaration); - types.push(ty); - } + let mut types = declarations.filter_map( + |(declaration, visibility_constraints, visibility_constraint)| { + let declaration = declaration?; + let static_visibility = visibility_constraints.evaluate(db, visibility_constraint); - let mut types = types.into_iter(); + if static_visibility.is_always_false() { + None + } else { + Some(declaration_ty(db, declaration)) + } + }, + ); if let Some(first) = types.next() { let mut conflicting: Vec> = vec![]; @@ -388,7 +393,7 @@ fn declarations_ty<'db>( first }; if conflicting.is_empty() { - let boundness = match unbound_visibility { + let boundness = match undeclared_visibility { Truthiness::AlwaysTrue => { unreachable!("If we have at least one declaration, the scope-start should not be definitely visible") } From c0e7c087050bb5e9b6ed734824fab192c12b701e Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 19 Dec 2024 16:45:37 +0100 Subject: [PATCH 52/65] Short circuit if declared type is available --- crates/red_knot_python_semantic/src/types.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 53affa43b46d4..bc9fa55d6e5de 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -75,19 +75,18 @@ fn symbol_by_id<'db>(db: &'db dyn Db, scope: ScopeId<'db>, symbol: ScopedSymbolI // If the symbol is declared, the public type is based on declarations; otherwise, it's based // on inference from bindings. - // let declaredness = use_def.public_declarations(symbol).declaredness(db); let declarations = use_def.public_declarations(symbol); let declared_ty = declarations_ty(db, declarations); - let bindings = use_def.public_bindings(symbol); // TODO: short circuit if declaredness is Bound - let inferred_ty = bindings_ty(db, bindings); - match declared_ty { // Symbol is declared, trust the declared type Ok(symbol @ Symbol::Type(_, Boundness::Bound)) => symbol, // Symbol is possibly declared Ok(Symbol::Type(declared_ty, Boundness::PossiblyUnbound)) => { + let bindings = use_def.public_bindings(symbol); + let inferred_ty = bindings_ty(db, bindings); + match inferred_ty { // Symbol is possibly undeclared and definitely unbound Symbol::Unbound => Symbol::Type(declared_ty, Boundness::Bound), @@ -100,8 +99,11 @@ fn symbol_by_id<'db>(db: &'db dyn Db, scope: ScopeId<'db>, symbol: ScopedSymbolI ), } } - // Symbol is undeclared - Ok(Symbol::Unbound) => inferred_ty, + // Symbol is undeclared, return the inferred type + Ok(Symbol::Unbound) => { + let bindings = use_def.public_bindings(symbol); + bindings_ty(db, bindings) + } // Symbol is possibly undeclared Err((declared_ty, _)) => { // Intentionally ignore conflicting declared types; that's not our problem, From 89fefe01643ef30a2e24c4c00cc4b6ed7ae80a97 Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 19 Dec 2024 16:52:44 +0100 Subject: [PATCH 53/65] Always build a union of inferred_ty and declared_ty --- crates/red_knot_python_semantic/src/types.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index bc9fa55d6e5de..8c1e36bdcf90d 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -90,12 +90,10 @@ fn symbol_by_id<'db>(db: &'db dyn Db, scope: ScopeId<'db>, symbol: ScopedSymbolI match inferred_ty { // Symbol is possibly undeclared and definitely unbound Symbol::Unbound => Symbol::Type(declared_ty, Boundness::Bound), - // Symbol is possibly undeclared and definitely bound - inferred @ Symbol::Type(_, Boundness::Bound) => inferred, - // Symbol is possibly undeclared and possibly unbound - Symbol::Type(inferred_ty, Boundness::PossiblyUnbound) => Symbol::Type( - UnionType::from_elements(db, [declared_ty, inferred_ty].iter().copied()), - Boundness::PossiblyUnbound, + // Symbol is possibly undeclared and (possibly) bound + Symbol::Type(inferred_ty, boundness) => Symbol::Type( + UnionType::from_elements(db, [inferred_ty, declared_ty].iter().copied()), + boundness, ), } } From 0b70c1c15997abbd1309aa3d96a2af94d711ebba Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 19 Dec 2024 17:32:04 +0100 Subject: [PATCH 54/65] Raise diagnostic for possibly-undeclared symbols --- .../resources/mdtest/import/conditional.md | 2 ++ crates/red_knot_python_semantic/src/types.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/import/conditional.md b/crates/red_knot_python_semantic/resources/mdtest/import/conditional.md index 79686f8e74676..0adb7ab83a874 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/import/conditional.md +++ b/crates/red_knot_python_semantic/resources/mdtest/import/conditional.md @@ -69,6 +69,8 @@ if coinflip(): ``` ```py +# TODO: We should potentially distinguish between possibly-unbound and possibly-undeclared here: +# error: [possibly-unbound-import] from maybe_undeclared import x reveal_type(x) # revealed: int diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 8c1e36bdcf90d..bf2f57e4799aa 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -89,7 +89,7 @@ fn symbol_by_id<'db>(db: &'db dyn Db, scope: ScopeId<'db>, symbol: ScopedSymbolI match inferred_ty { // Symbol is possibly undeclared and definitely unbound - Symbol::Unbound => Symbol::Type(declared_ty, Boundness::Bound), + Symbol::Unbound => Symbol::Type(declared_ty, Boundness::PossiblyUnbound), // Symbol is possibly undeclared and (possibly) bound Symbol::Type(inferred_ty, boundness) => Symbol::Type( UnionType::from_elements(db, [inferred_ty, declared_ty].iter().copied()), From 2c104c695505f0f8482dba38f64b9983ce17fdb0 Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 19 Dec 2024 17:34:10 +0100 Subject: [PATCH 55/65] Add section for yet-unsupported features --- .../resources/mdtest/statically_known_branches.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md b/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md index 11e97d17480d0..ffbf197680bb1 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md +++ b/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md @@ -1455,3 +1455,14 @@ if True: # no error from module import symbol ``` + +## Unsupported features + +We do not support full unreachable code analysis yet. We also raise diagnostics from +statically-known to be false branches: + +```py +if False: + # error: [unresolved-reference] + x +``` From 025804e548322c08b50031346d185706eac78627 Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 19 Dec 2024 19:48:41 +0100 Subject: [PATCH 56/65] Simplify code in simplify_visibility_constraints --- .../src/semantic_index/use_def.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index 0b022615b2eae..12b40b741f7ee 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -560,13 +560,14 @@ impl<'db> UseDefMapBuilder<'db> { self.scope_start_visibility = snapshot.scope_start_visibility; - let mut snapshot_definitions_iter = snapshot.symbol_states.into_iter(); - for current in &mut self.symbol_states { - if let Some(snapshot) = snapshot_definitions_iter.next() { - current.simplify_visibility_constraints(snapshot); - } else { - // Symbol not present in snapshot, keep visibility constraints - } + // 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 + // be subject to visibility constraints. We only simplify/reset visibility constraints + // for symbols 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); } } From 2dfe6820f0a58d39b51ca30398f9ac5352834b3f Mon Sep 17 00:00:00 2001 From: David Peter Date: Fri, 20 Dec 2024 09:25:43 +0100 Subject: [PATCH 57/65] Use actual return values in dummy functions --- .../mdtest/statically_known_branches.md | 91 +++++++++++++------ 1 file changed, 61 insertions(+), 30 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md b/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md index ffbf197680bb1..46ccf784d68bc 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md +++ b/crates/red_knot_python_semantic/resources/mdtest/statically_known_branches.md @@ -250,7 +250,8 @@ reveal_type(x) # revealed: Literal[2] Just for comparison, we still infer the combined type if the condition is not statically known: ```py -def flag() -> bool: ... +def flag() -> bool: + return True x = 1 @@ -278,7 +279,8 @@ reveal_type(x) # revealed: Literal[2] #### Always false ```py -def flag() -> bool: ... +def flag() -> bool: + return True x = 1 @@ -295,7 +297,8 @@ reveal_type(x) # revealed: Literal[2, 4] #### Always true ```py -def flag() -> bool: ... +def flag() -> bool: + return True x = 1 @@ -312,7 +315,8 @@ reveal_type(x) # revealed: Literal[2, 3] #### Ambiguous ```py -def flag() -> bool: ... +def flag() -> bool: + return True x = 1 @@ -331,7 +335,8 @@ reveal_type(x) # revealed: Literal[2, 3, 4] Make sure that we include bindings from all non-`False` branches: ```py -def flag() -> bool: ... +def flag() -> bool: + return True x = 1 @@ -358,7 +363,8 @@ reveal_type(x) # revealed: Literal[2, 3, 6, 7, 8] Make sure that we only include the binding from the first `elif True` branch: ```py -def flag() -> bool: ... +def flag() -> bool: + return True x = 1 @@ -381,7 +387,8 @@ reveal_type(x) # revealed: Literal[2, 3, 4] #### `elif` without `else` branch, always true ```py -def flag() -> bool: ... +def flag() -> bool: + return True x = 1 @@ -396,7 +403,8 @@ reveal_type(x) # revealed: Literal[2, 3] #### `elif` without `else` branch, always false ```py -def flag() -> bool: ... +def flag() -> bool: + return True x = 1 @@ -441,7 +449,8 @@ reveal_type(x) # revealed: Literal[1] #### `if ` inside `if True` ```py -def flag() -> bool: ... +def flag() -> bool: + return True x = 1 @@ -457,7 +466,8 @@ reveal_type(x) # revealed: Literal[1, 2] #### `if True` inside `if ` ```py -def flag() -> bool: ... +def flag() -> bool: + return True x = 1 @@ -501,7 +511,8 @@ reveal_type(x) # revealed: Literal[1] #### `if ` inside `if False` ... `else` ```py -def flag() -> bool: ... +def flag() -> bool: + return True x = 1 @@ -551,7 +562,8 @@ reveal_type(x) # revealed: Literal[3] #### `if ` inside `if True` ```py -def flag() -> bool: ... +def flag() -> bool: + return True x = 1 @@ -569,7 +581,8 @@ reveal_type(x) # revealed: Literal[2, 3] #### `if True` inside `if ` ```py -def flag() -> bool: ... +def flag() -> bool: + return True x = 1 @@ -619,7 +632,8 @@ reveal_type(x) # revealed: Literal[4] #### `if ` inside `if False` ... `else` ```py -def flag() -> bool: ... +def flag() -> bool: + return True x = 1 @@ -727,7 +741,8 @@ reveal_type(x) # revealed: Literal[5] ##### `if True` inside `for` ```py -def iterable() -> list[object]: ... +def iterable() -> list[object]: + return [1, ""] x = 1 @@ -742,7 +757,8 @@ reveal_type(x) # revealed: Literal[1, 3] ##### `if True` inside `for` ... `else` ```py -def iterable() -> list[object]: ... +def iterable() -> list[object]: + return [1, ""] x = 1 @@ -760,7 +776,8 @@ reveal_type(x) # revealed: Literal[3] ##### `for` inside `if True` ```py -def iterable() -> list[object]: ... +def iterable() -> list[object]: + return [1, ""] x = 1 @@ -776,7 +793,8 @@ reveal_type(x) # revealed: Literal[1, 2] ##### `for` ... `else` inside `if True` ```py -def iterable() -> list[object]: ... +def iterable() -> list[object]: + return [1, ""] x = 1 @@ -794,7 +812,8 @@ reveal_type(x) # revealed: Literal[3] ##### `for` loop with `break` inside `if True` ```py -def iterable() -> list[object]: ... +def iterable() -> list[object]: + return [1, ""] x = 1 @@ -920,7 +939,8 @@ reveal_type(x) # revealed: Literal[2] Make sure that we still infer the combined type if the condition is not statically known: ```py -def flag() -> bool: ... +def flag() -> bool: + return True x = 1 @@ -1006,7 +1026,8 @@ reveal_type(x) # revealed: Literal[2] Make sure we don't infer a static truthiness in case there is a case guard: ```py -def flag() -> bool: ... +def flag() -> bool: + return True x = 1 @@ -1056,7 +1077,8 @@ reveal_type(x) # revealed: Literal[1] For definitely-false cases, the presence of a guard has no influence: ```py -def flag() -> bool: ... +def flag() -> bool: + return True x = 1 @@ -1203,7 +1225,8 @@ def f() -> None: ### Ambiguous ```py -def flag() -> bool: ... +def flag() -> bool: + return True x: str @@ -1217,14 +1240,19 @@ def f() -> None: ## Conditional function definitions ```py -def f() -> int: ... -def g() -> int: ... +def f() -> int: + return 1 + +def g() -> int: + return 1 if True: - def f() -> str: ... + def f() -> str: + return "" else: - def g() -> str: ... + def g() -> str: + return "" reveal_type(f()) # revealed: str reveal_type(g()) # revealed: int @@ -1308,7 +1336,8 @@ For comparison, we still detect definitions inside non-statically known branches unbound: ```py -def flag() -> bool: ... +def flag() -> bool: + return True if flag(): x = 1 @@ -1320,7 +1349,8 @@ x ### Nested conditionals ```py -def flag() -> bool: ... +def flag() -> bool: + return True if False: if True: @@ -1419,7 +1449,8 @@ from module import symbol #### Ambiguous, possibly unbound ```py path=module.py -def flag() -> bool: ... +def flag() -> bool: + return True if flag(): symbol = 1 From 3dde38223629db281c974dea53326ecddbd70152 Mon Sep 17 00:00:00 2001 From: David Peter Date: Fri, 20 Dec 2024 09:32:09 +0100 Subject: [PATCH 58/65] Use struct instead of three-tuple --- .../src/semantic_index.rs | 4 +-- .../src/semantic_index/use_def.rs | 26 ++++++++++--------- crates/red_knot_python_semantic/src/types.rs | 25 ++++++++++++------ 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index.rs b/crates/red_knot_python_semantic/src/semantic_index.rs index 14bb11030d962..afcaafd339542 100644 --- a/crates/red_knot_python_semantic/src/semantic_index.rs +++ b/crates/red_knot_python_semantic/src/semantic_index.rs @@ -29,8 +29,8 @@ pub mod symbol; mod use_def; pub(crate) use self::use_def::{ - BindingWithConstraints, BindingWithConstraintsIterator, DeclarationsIterator, - ScopedVisibilityConstraintId, + BindingWithConstraints, BindingWithConstraintsIterator, DeclarationWithConstraint, + DeclarationsIterator, ScopedVisibilityConstraintId, }; type SymbolMap = hashbrown::HashMap; diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index 12b40b741f7ee..a7ecedddcbc78 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -412,21 +412,23 @@ pub(crate) struct DeclarationsIterator<'map, 'db> { inner: DeclarationIdIterator<'map>, } +pub(crate) struct DeclarationWithConstraint<'map, 'db> { + pub(crate) declaration: Option>, + pub(crate) visibility_constraints: &'map VisibilityConstraints<'db>, + pub(crate) visibility_constraint: ScopedVisibilityConstraintId, +} + impl<'map, 'db> Iterator for DeclarationsIterator<'map, 'db> { - type Item = ( - Option>, - &'map VisibilityConstraints<'db>, - ScopedVisibilityConstraintId, - ); + type Item = DeclarationWithConstraint<'map, 'db>; fn next(&mut self) -> Option { - self.inner.next().map(|(def_id, visibility_constraint_id)| { - ( - self.all_definitions[def_id], - self.visibility_constraints, - visibility_constraint_id, - ) - }) + self.inner.next().map( + |(def_id, visibility_constraint)| DeclarationWithConstraint { + declaration: self.all_definitions[def_id], + visibility_constraints: self.visibility_constraints, + visibility_constraint, + }, + ) } } diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index bf2f57e4799aa..9c4b44927aece 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -23,7 +23,8 @@ use crate::semantic_index::definition::Definition; use crate::semantic_index::symbol::{self as symbol, ScopeId, ScopedSymbolId}; use crate::semantic_index::{ global_scope, imported_modules, semantic_index, symbol_table, use_def_map, - BindingWithConstraints, BindingWithConstraintsIterator, DeclarationsIterator, + BindingWithConstraints, BindingWithConstraintsIterator, DeclarationWithConstraint, + DeclarationsIterator, }; use crate::stdlib::{builtins_symbol, known_module_symbol, typing_extensions_symbol}; use crate::symbol::{Boundness, Symbol}; @@ -358,15 +359,23 @@ fn declarations_ty<'db>( ) -> DeclaredTypeResult<'db> { let mut declarations = declarations.peekable(); - let undeclared_visibility = - if let Some((None, visibility_constraints, visibility_constraint)) = declarations.peek() { - visibility_constraints.evaluate(db, *visibility_constraint) - } else { - Truthiness::AlwaysFalse - }; + let undeclared_visibility = if let Some(DeclarationWithConstraint { + declaration: None, + visibility_constraints, + visibility_constraint, + }) = declarations.peek() + { + visibility_constraints.evaluate(db, *visibility_constraint) + } else { + Truthiness::AlwaysFalse + }; let mut types = declarations.filter_map( - |(declaration, visibility_constraints, visibility_constraint)| { + |DeclarationWithConstraint { + declaration, + visibility_constraints, + visibility_constraint, + }| { let declaration = declaration?; let static_visibility = visibility_constraints.evaluate(db, visibility_constraint); From df3536969fa7dd26d3e49e00c160bbd29e7751a2 Mon Sep 17 00:00:00 2001 From: David Peter Date: Fri, 20 Dec 2024 09:34:42 +0100 Subject: [PATCH 59/65] ::default() instead of ::new() --- .../red_knot_python_semantic/src/semantic_index/builder.rs | 2 +- .../red_knot_python_semantic/src/semantic_index/use_def.rs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index/builder.rs b/crates/red_knot_python_semantic/src/semantic_index/builder.rs index ef42aed58b665..3eac622903ced 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder.rs @@ -161,7 +161,7 @@ impl<'db> SemanticIndexBuilder<'db> { let file_scope_id = self.scopes.push(scope); self.symbol_tables.push(SymbolTableBuilder::default()); - self.use_def_maps.push(UseDefMapBuilder::new()); + self.use_def_maps.push(UseDefMapBuilder::default()); let ast_id_scope = self.ast_ids.push(AstIdsBuilder::default()); let scope_id = ScopeId::new(self.db, self.file, file_scope_id, countme::Count::default()); diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index a7ecedddcbc78..85cc30be43e96 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -468,8 +468,8 @@ pub(super) struct UseDefMapBuilder<'db> { symbol_states: IndexVec, } -impl<'db> UseDefMapBuilder<'db> { - pub(super) fn new() -> Self { +impl Default for UseDefMapBuilder<'_> { + fn default() -> Self { Self { all_definitions: IndexVec::from_iter([None]), all_constraints: IndexVec::new(), @@ -480,7 +480,9 @@ impl<'db> UseDefMapBuilder<'db> { symbol_states: IndexVec::new(), } } +} +impl<'db> UseDefMapBuilder<'db> { pub(super) fn add_symbol(&mut self, symbol: ScopedSymbolId) { let new_symbol = self .symbol_states From 03818856eb5adf458c4fa3d445c9ff9a0ae2ac8e Mon Sep 17 00:00:00 2001 From: David Peter Date: Fri, 20 Dec 2024 09:36:53 +0100 Subject: [PATCH 60/65] Minor review comments --- .../src/semantic_index/use_def.rs | 3 +-- .../src/semantic_index/use_def/symbol_state.rs | 2 +- crates/red_knot_python_semantic/src/types.rs | 8 ++++---- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index 85cc30be43e96..8459a5f515cf7 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -559,8 +559,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) { - let num_symbols = self.symbol_states.len(); - debug_assert!(num_symbols >= snapshot.symbol_states.len()); + debug_assert!(self.symbol_states.len() >= snapshot.symbol_states.len()); self.scope_start_visibility = snapshot.scope_start_visibility; diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs index 87be32f3b08d5..a6f8f8d12faaf 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs @@ -244,7 +244,7 @@ impl SymbolState { /// Record a newly-encountered binding for this symbol. pub(super) fn record_binding(&mut self, binding_id: ScopedDefinitionId) { - debug_assert!(binding_id != ScopedDefinitionId::UNBOUND); + debug_assert_ne!(binding_id, ScopedDefinitionId::UNBOUND); self.bindings.record_binding(binding_id); } diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 9c4b44927aece..a96596330f4fc 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -78,17 +78,17 @@ fn symbol_by_id<'db>(db: &'db dyn Db, scope: ScopeId<'db>, symbol: ScopedSymbolI // on inference from bindings. let declarations = use_def.public_declarations(symbol); - let declared_ty = declarations_ty(db, declarations); + let declared = declarations_ty(db, declarations); - match declared_ty { + match declared { // Symbol is declared, trust the declared type Ok(symbol @ Symbol::Type(_, Boundness::Bound)) => symbol, // Symbol is possibly declared Ok(Symbol::Type(declared_ty, Boundness::PossiblyUnbound)) => { let bindings = use_def.public_bindings(symbol); - let inferred_ty = bindings_ty(db, bindings); + let inferred = bindings_ty(db, bindings); - match inferred_ty { + match inferred { // Symbol is possibly undeclared and definitely unbound Symbol::Unbound => Symbol::Type(declared_ty, Boundness::PossiblyUnbound), // Symbol is possibly undeclared and (possibly) bound From 0d6425ae4e9f5cf3d25686adaafb44aab144144c Mon Sep 17 00:00:00 2001 From: David Peter Date: Fri, 20 Dec 2024 09:38:24 +0100 Subject: [PATCH 61/65] Use KnownModule --- crates/red_knot_python_semantic/src/types.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index a96596330f4fc..c1ce738c445ea 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -138,7 +138,8 @@ fn symbol<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str) -> Symbol<'db> return Symbol::Type(Type::BooleanLiteral(true), Boundness::Bound); } if name == "platform" - && file_to_module(db, scope.file(db)).is_some_and(|module| module.name() == "sys") + && file_to_module(db, scope.file(db)) + .is_some_and(|module| module.is_known(KnownModule::Sys)) { match Program::get(db).python_platform(db) { crate::PythonPlatform::Identifier(platform) => { From b85a56db5b987945fb31ed3d2becc53d160bc3b7 Mon Sep 17 00:00:00 2001 From: David Peter Date: Fri, 20 Dec 2024 09:40:43 +0100 Subject: [PATCH 62/65] Minor iterator changes --- crates/red_knot_python_semantic/src/semantic_index.rs | 8 ++------ crates/red_knot_python_semantic/src/types.rs | 4 ++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index.rs b/crates/red_knot_python_semantic/src/semantic_index.rs index afcaafd339542..b975966c2db33 100644 --- a/crates/red_knot_python_semantic/src/semantic_index.rs +++ b/crates/red_knot_python_semantic/src/semantic_index.rs @@ -379,16 +379,12 @@ mod tests { impl UseDefMap<'_> { fn first_public_binding(&self, symbol: ScopedSymbolId) -> Option> { self.public_bindings(symbol) - .map(|constrained_binding| constrained_binding.binding) - .find(Option::is_some) - .unwrap() + .find_map(|constrained_binding| constrained_binding.binding) } fn first_binding_at_use(&self, use_id: ScopedUseId) -> Option> { self.bindings_at_use(use_id) - .map(|constrained_binding| constrained_binding.binding) - .find(Option::is_some) - .unwrap() + .find_map(|constrained_binding| constrained_binding.binding) } } diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index c1ce738c445ea..226174bcb22a3 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -392,7 +392,7 @@ fn declarations_ty<'db>( let mut conflicting: Vec> = vec![]; let declared_ty = if let Some(second) = types.next() { let mut builder = UnionBuilder::new(db).add(first); - for other in [second].into_iter().chain(types) { + for other in std::iter::once(second).chain(types) { if !first.is_equivalent_to(db, other) { conflicting.push(other); } @@ -415,7 +415,7 @@ fn declarations_ty<'db>( } else { Err(( declared_ty, - [first].into_iter().chain(conflicting).collect(), + std::iter::once(first).chain(conflicting).collect(), )) } } else { From f0e9cce52a82529f3a29fbeef87bb9e4039cb928 Mon Sep 17 00:00:00 2001 From: David Peter Date: Fri, 20 Dec 2024 09:44:21 +0100 Subject: [PATCH 63/65] More minor changes --- .../src/semantic_index/use_def.rs | 2 +- .../src/semantic_index/use_def/symbol_state.rs | 6 +++--- crates/red_knot_python_semantic/src/types/infer.rs | 8 +++----- .../src/visibility_constraints.rs | 6 ++++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index 8459a5f515cf7..a455c4b8a85c8 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -473,7 +473,7 @@ impl Default for UseDefMapBuilder<'_> { Self { all_definitions: IndexVec::from_iter([None]), all_constraints: IndexVec::new(), - visibility_constraints: VisibilityConstraints::new(), + visibility_constraints: VisibilityConstraints::default(), scope_start_visibility: ScopedVisibilityConstraintId::ALWAYS_TRUE, bindings_by_use: IndexVec::new(), definitions_by_definition: FxHashMap::default(), diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs index a6f8f8d12faaf..c6c2d2deac506 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs @@ -646,7 +646,7 @@ mod tests { #[test] fn merge() { - let mut visibility_constraints = VisibilityConstraints::new(); + let mut visibility_constraints = VisibilityConstraints::default(); // merging the same definition with the same constraint keeps the constraint let mut sym1a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); @@ -717,7 +717,7 @@ mod tests { #[test] fn record_declaration_merge() { - let mut visibility_constraints = VisibilityConstraints::new(); + let mut visibility_constraints = VisibilityConstraints::default(); let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym.record_declaration(ScopedDefinitionId::from_u32(1)); @@ -731,7 +731,7 @@ mod tests { #[test] fn record_declaration_merge_partial_undeclared() { - let mut visibility_constraints = VisibilityConstraints::new(); + let mut visibility_constraints = VisibilityConstraints::default(); let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); sym.record_declaration(ScopedDefinitionId::from_u32(1)); diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 64b16a0ad641d..3aafa78ae922b 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -6361,15 +6361,13 @@ mod tests { } // Incremental inference tests - + #[track_caller] fn first_public_binding<'db>(db: &'db TestDb, file: File, name: &str) -> Definition<'db> { let scope = global_scope(db, file); use_def_map(db, scope) .public_bindings(symbol_table(db, scope).symbol_id_by_name(name).unwrap()) - .map(|b| b.binding) - .find(Option::is_some) - .unwrap() - .unwrap() + .find_map(|b| b.binding) + .expect("no binding found") } #[test] diff --git a/crates/red_knot_python_semantic/src/visibility_constraints.rs b/crates/red_knot_python_semantic/src/visibility_constraints.rs index 66044b7ec8957..7c1af890f3bfb 100644 --- a/crates/red_knot_python_semantic/src/visibility_constraints.rs +++ b/crates/red_knot_python_semantic/src/visibility_constraints.rs @@ -25,13 +25,15 @@ pub(crate) struct VisibilityConstraints<'db> { constraints: IndexVec>, } -impl<'db> VisibilityConstraints<'db> { - pub(crate) fn new() -> Self { +impl Default for VisibilityConstraints<'_> { + fn default() -> Self { Self { constraints: IndexVec::from_iter([VisibilityConstraint::AlwaysTrue]), } } +} +impl<'db> VisibilityConstraints<'db> { pub(crate) fn add( &mut self, constraint: VisibilityConstraint<'db>, From fafecc97e4b5aabfe1a2e9aa401abda365fc04a8 Mon Sep 17 00:00:00 2001 From: David Peter Date: Fri, 20 Dec 2024 10:18:36 +0100 Subject: [PATCH 64/65] Revert possibly-unbound changes --- .../resources/mdtest/import/conditional.md | 2 -- crates/red_knot_python_semantic/src/types.rs | 7 ++++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/import/conditional.md b/crates/red_knot_python_semantic/resources/mdtest/import/conditional.md index 0adb7ab83a874..79686f8e74676 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/import/conditional.md +++ b/crates/red_knot_python_semantic/resources/mdtest/import/conditional.md @@ -69,8 +69,6 @@ if coinflip(): ``` ```py -# TODO: We should potentially distinguish between possibly-unbound and possibly-undeclared here: -# error: [possibly-unbound-import] from maybe_undeclared import x reveal_type(x) # revealed: int diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 226174bcb22a3..7421f2367709b 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -90,7 +90,12 @@ fn symbol_by_id<'db>(db: &'db dyn Db, scope: ScopeId<'db>, symbol: ScopedSymbolI match inferred { // Symbol is possibly undeclared and definitely unbound - Symbol::Unbound => Symbol::Type(declared_ty, Boundness::PossiblyUnbound), + Symbol::Unbound => { + // TODO: We probably don't want to report `Bound` here. This requires a bit of + // design work though as we might want a different behavior for stubs and for + // normal modules. + Symbol::Type(declared_ty, Boundness::Bound) + } // Symbol is possibly undeclared and (possibly) bound Symbol::Type(inferred_ty, boundness) => Symbol::Type( UnionType::from_elements(db, [inferred_ty, declared_ty].iter().copied()), From b0986debd315f80269c9a862a1bc9016bf085cdb Mon Sep 17 00:00:00 2001 From: David Peter Date: Fri, 20 Dec 2024 11:36:21 +0100 Subject: [PATCH 65/65] Interned visibility constraints --- .../src/semantic_index.rs | 4 +- .../src/semantic_index/builder.rs | 151 +++--- .../semantic_index/builder/except_handlers.rs | 16 +- .../src/semantic_index/constraint.rs | 4 +- .../src/semantic_index/use_def.rs | 143 +++--- .../semantic_index/use_def/symbol_state.rs | 443 +++++++++--------- crates/red_knot_python_semantic/src/types.rs | 14 +- .../src/visibility_constraints.rs | 257 +++++----- 8 files changed, 500 insertions(+), 532 deletions(-) diff --git a/crates/red_knot_python_semantic/src/semantic_index.rs b/crates/red_knot_python_semantic/src/semantic_index.rs index b975966c2db33..c9b05a4dd2b9e 100644 --- a/crates/red_knot_python_semantic/src/semantic_index.rs +++ b/crates/red_knot_python_semantic/src/semantic_index.rs @@ -30,7 +30,7 @@ mod use_def; pub(crate) use self::use_def::{ BindingWithConstraints, BindingWithConstraintsIterator, DeclarationWithConstraint, - DeclarationsIterator, ScopedVisibilityConstraintId, + DeclarationsIterator, }; type SymbolMap = hashbrown::HashMap; @@ -156,7 +156,7 @@ impl<'db> SemanticIndex<'db> { /// Use the Salsa cached [`use_def_map()`] query if you only need the /// use-def map for a single scope. #[track_caller] - pub(super) fn use_def_map(&self, scope_id: FileScopeId) -> Arc { + pub(super) fn use_def_map(&'db self, scope_id: FileScopeId) -> Arc> { self.use_def_maps[scope_id].clone() } diff --git a/crates/red_knot_python_semantic/src/semantic_index/builder.rs b/crates/red_knot_python_semantic/src/semantic_index/builder.rs index 3eac622903ced..7781662a663ab 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder.rs @@ -25,9 +25,7 @@ use crate::semantic_index::symbol::{ FileScopeId, NodeWithScopeKey, NodeWithScopeRef, Scope, ScopeId, ScopedSymbolId, SymbolTableBuilder, }; -use crate::semantic_index::use_def::{ - FlowSnapshot, ScopedVisibilityConstraintId, UseDefMapBuilder, -}; +use crate::semantic_index::use_def::{FlowSnapshot, UseDefMapBuilder}; use crate::semantic_index::SemanticIndex; use crate::unpack::Unpack; use crate::visibility_constraints::VisibilityConstraint; @@ -67,9 +65,9 @@ pub(super) struct SemanticIndexBuilder<'db> { current_match_case: Option>, /// Flow states at each `break` in the current loop. - loop_break_states: Vec, + loop_break_states: Vec>, /// Per-scope contexts regarding nested `try`/`except` statements - try_node_context_stack_manager: TryNodeContextStackManager, + try_node_context_stack_manager: TryNodeContextStackManager<'db>, /// Flags about the file's global scope has_future_annotations: bool, @@ -161,7 +159,7 @@ impl<'db> SemanticIndexBuilder<'db> { let file_scope_id = self.scopes.push(scope); self.symbol_tables.push(SymbolTableBuilder::default()); - self.use_def_maps.push(UseDefMapBuilder::default()); + self.use_def_maps.push(UseDefMapBuilder::new(self.db)); let ast_id_scope = self.ast_ids.push(AstIdsBuilder::default()); let scope_id = ScopeId::new(self.db, self.file, file_scope_id, countme::Count::default()); @@ -204,16 +202,17 @@ impl<'db> SemanticIndexBuilder<'db> { &mut self.ast_ids[scope_id] } - fn flow_snapshot(&self) -> FlowSnapshot { + fn flow_snapshot(&'db self) -> FlowSnapshot<'db> { self.current_use_def_map().snapshot() } - fn flow_restore(&mut self, state: FlowSnapshot) { + fn flow_restore(&'db mut self, state: FlowSnapshot<'db>) { self.current_use_def_map_mut().restore(state); } - fn flow_merge(&mut self, state: FlowSnapshot) { - self.current_use_def_map_mut().merge(state); + fn flow_merge(&mut self, state: FlowSnapshot<'db>) { + let db = self.db; + self.current_use_def_map_mut().merge(db, state); } fn add_symbol(&mut self, name: Name) -> ScopedSymbolId { @@ -237,7 +236,8 @@ impl<'db> SemanticIndexBuilder<'db> { } fn add_definition( - &mut self, + &'db mut self, + db: &'db dyn Db, symbol: ScopedSymbolId, definition_node: impl Into>, ) -> Definition<'db> { @@ -270,10 +270,10 @@ impl<'db> SemanticIndexBuilder<'db> { 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(db, symbol, definition); } - DefinitionCategory::Declaration => use_def.record_declaration(symbol, definition), - DefinitionCategory::Binding => use_def.record_binding(symbol, definition), + DefinitionCategory::Declaration => use_def.record_declaration(db, symbol, definition), + DefinitionCategory::Binding => use_def.record_binding(db, symbol, definition), } let mut try_node_stack_manager = std::mem::take(&mut self.try_node_context_stack_manager); @@ -321,43 +321,45 @@ impl<'db> SemanticIndexBuilder<'db> { negated } - fn add_visibility_constraint( + fn record_visibility_constraint( &mut self, + db: &'db dyn Db, constraint: VisibilityConstraint<'db>, - ) -> ScopedVisibilityConstraintId { - self.current_use_def_map_mut() - .add_visibility_constraint(constraint) - } - - fn record_visibility_constraint_id(&mut self, constraint: ScopedVisibilityConstraintId) { + ) { self.current_use_def_map_mut() - .record_visibility_constraint_id(constraint); + .record_visibility_constraint(db, constraint); } - fn record_visibility_constraint( + fn create_and_record_visibility_constraint( &mut self, + db: &'db dyn Db, constraint: Constraint<'db>, - ) -> ScopedVisibilityConstraintId { + ) -> VisibilityConstraint<'db> { + let constraint = VisibilityConstraint::visible_if(db, constraint); self.current_use_def_map_mut() - .record_visibility_constraint(VisibilityConstraint::VisibleIf(constraint)) + .record_visibility_constraint(db, constraint); + constraint } - fn record_ambiguous_visibility(&mut self) -> ScopedVisibilityConstraintId { + fn record_ambiguous_visibility(&mut self, db: &'db dyn Db) { self.current_use_def_map_mut() - .record_visibility_constraint(VisibilityConstraint::Ambiguous) + .record_visibility_constraint(db, VisibilityConstraint::ambiguous(db)); } - fn simplify_visibility_constraints(&mut self, snapshot: FlowSnapshot) { + fn simplify_visibility_constraints(&mut self, snapshot: FlowSnapshot<'db>) { self.current_use_def_map_mut() .simplify_visibility_constraints(snapshot); } fn record_negated_visibility_constraint( &mut self, - constraint: ScopedVisibilityConstraintId, - ) -> ScopedVisibilityConstraintId { + db: &'db dyn Db, + constraint: VisibilityConstraint<'db>, + ) -> VisibilityConstraint<'db> { + let constraint = VisibilityConstraint::visible_if_not(db, constraint); self.current_use_def_map_mut() - .record_visibility_constraint(VisibilityConstraint::VisibleIfNot(constraint)) + .record_visibility_constraint(db, constraint); + constraint } fn push_assignment(&mut self, assignment: CurrentAssignment<'db>) { @@ -469,9 +471,11 @@ impl<'db> SemanticIndexBuilder<'db> { self.visit_expr(default); } match type_param { - ast::TypeParam::TypeVar(node) => self.add_definition(symbol, node), - ast::TypeParam::ParamSpec(node) => self.add_definition(symbol, node), - ast::TypeParam::TypeVarTuple(node) => self.add_definition(symbol, node), + ast::TypeParam::TypeVar(node) => self.add_definition(self.db, symbol, node), + ast::TypeParam::ParamSpec(node) => self.add_definition(self.db, symbol, node), + ast::TypeParam::TypeVarTuple(node) => { + self.add_definition(self.db, symbol, node) + } }; } } @@ -552,20 +556,25 @@ impl<'db> SemanticIndexBuilder<'db> { if let Some(vararg) = parameters.vararg.as_ref() { let symbol = self.add_symbol(vararg.name.id().clone()); self.add_definition( + self.db, symbol, DefinitionNodeRef::VariadicPositionalParameter(vararg), ); } if let Some(kwarg) = parameters.kwarg.as_ref() { let symbol = self.add_symbol(kwarg.name.id().clone()); - self.add_definition(symbol, DefinitionNodeRef::VariadicKeywordParameter(kwarg)); + self.add_definition( + self.db, + symbol, + DefinitionNodeRef::VariadicKeywordParameter(kwarg), + ); } } fn declare_parameter(&mut self, parameter: &'db ast::ParameterWithDefault) { let symbol = self.add_symbol(parameter.parameter.name.id().clone()); - let definition = self.add_definition(symbol, parameter); + let definition = self.add_definition(self.db, symbol, parameter); // Insert a mapping from the inner Parameter node to the same definition. // This ensures that calling `HasTy::ty` on the inner parameter returns @@ -680,7 +689,7 @@ where // at the end to match the runtime evaluation of parameter defaults // and return-type annotations. let symbol = self.add_symbol(name.id.clone()); - self.add_definition(symbol, function_def); + self.add_definition(self.db, symbol, function_def); } ast::Stmt::ClassDef(class) => { for decorator in &class.decorator_list { @@ -688,7 +697,7 @@ where } let symbol = self.add_symbol(class.name.id.clone()); - self.add_definition(symbol, class); + self.add_definition(self.db, symbol, class); self.with_type_params( NodeWithScopeRef::ClassTypeParameters(class), @@ -713,7 +722,7 @@ where .map(|name| name.id.clone()) .unwrap_or("".into()), ); - self.add_definition(symbol, type_alias); + self.add_definition(self.db, symbol, type_alias); self.visit_expr(&type_alias.name); self.with_type_params( @@ -741,7 +750,7 @@ where }; let symbol = self.add_symbol(symbol_name); - self.add_definition(symbol, alias); + self.add_definition(self.db, symbol, alias); } } ast::Stmt::ImportFrom(node) => { @@ -762,7 +771,11 @@ where let symbol = self.add_symbol(symbol_name.clone()); - self.add_definition(symbol, ImportFromDefinitionNodeRef { node, alias_index }); + self.add_definition( + self.db, + symbol, + ImportFromDefinitionNodeRef { node, alias_index }, + ); } } ast::Stmt::Assign(node) => { @@ -862,7 +875,8 @@ where let mut constraints = vec![constraint]; self.visit_body(&node.body); - let visibility_constraint_id = self.record_visibility_constraint(constraint); + let visibility_constraint_id = + self.create_and_record_visibility_constraint(self.db, constraint); let mut vis_constraints = vec![visibility_constraint_id]; let mut post_clauses: Vec = vec![]; @@ -903,10 +917,11 @@ where self.visit_body(clause_body); for id in &vis_constraints { - self.record_negated_visibility_constraint(*id); + self.record_negated_visibility_constraint(self.db, *id); } if let Some(elif_constraint) = elif_constraint { - let id = self.record_visibility_constraint(elif_constraint); + let id = + self.create_and_record_visibility_constraint(self.db, elif_constraint); vis_constraints.push(id); } } @@ -938,7 +953,8 @@ where self.visit_body(body); self.set_inside_loop(outer_loop_state); - let vis_constraint_id = self.record_visibility_constraint(constraint); + let vis_constraint_id = + self.create_and_record_visibility_constraint(self.db, constraint); // Get the break states from the body of this loop, and restore the saved outer // ones. @@ -950,14 +966,14 @@ where self.flow_merge(pre_loop.clone()); self.record_negated_constraint(constraint); self.visit_body(orelse); - self.record_negated_visibility_constraint(vis_constraint_id); + self.record_negated_visibility_constraint(self.db, vis_constraint_id); // Breaking out of a while loop bypasses the `else` clause, so merge in the break // states after visiting `else`. for break_state in break_states { let snapshot = self.flow_snapshot(); self.flow_restore(break_state); - self.record_visibility_constraint(constraint); + self.create_and_record_visibility_constraint(self.db, constraint); self.flow_merge(snapshot); } @@ -1002,7 +1018,7 @@ where self.add_standalone_expression(iter); self.visit_expr(iter); - self.record_ambiguous_visibility(); + self.record_ambiguous_visibility(self.db); let pre_loop = self.flow_snapshot(); let saved_break_states = std::mem::take(&mut self.loop_break_states); @@ -1056,7 +1072,7 @@ where self.visit_match_case(first); let first_vis_constraint_id = - self.record_visibility_constraint(first_constraint_id); + self.create_and_record_visibility_constraint(self.db, first_constraint_id); let mut vis_constraints = vec![first_vis_constraint_id]; let mut post_case_snapshots = vec![]; @@ -1071,9 +1087,10 @@ where self.visit_match_case(case); for id in &vis_constraints { - self.record_negated_visibility_constraint(*id); + self.record_negated_visibility_constraint(self.db, *id); } - let vis_constraint_id = self.record_visibility_constraint(constraint_id); + let vis_constraint_id = + self.create_and_record_visibility_constraint(self.db, constraint_id); vis_constraints.push(vis_constraint_id); } @@ -1087,7 +1104,7 @@ where self.flow_restore(after_subject.clone()); for id in &vis_constraints { - self.record_negated_visibility_constraint(*id); + self.record_negated_visibility_constraint(self.db, *id); } } @@ -1105,7 +1122,7 @@ where is_star, range: _, }) => { - self.record_ambiguous_visibility(); + self.record_ambiguous_visibility(self.db); // Save the state prior to visiting any of the `try` block. // @@ -1164,6 +1181,7 @@ where let symbol = self.add_symbol(symbol_name.id.clone()); self.add_definition( + self.db, symbol, DefinitionNodeRef::ExceptHandler(ExceptHandlerDefinitionNodeRef { handler: except_handler, @@ -1246,6 +1264,7 @@ where unpack, }) => { self.add_definition( + self.db, symbol, AssignmentDefinitionNodeRef { unpack, @@ -1256,13 +1275,14 @@ where ); } Some(CurrentAssignment::AnnAssign(ann_assign)) => { - self.add_definition(symbol, ann_assign); + self.add_definition(self.db, symbol, ann_assign); } Some(CurrentAssignment::AugAssign(aug_assign)) => { - self.add_definition(symbol, aug_assign); + self.add_definition(self.db, symbol, aug_assign); } Some(CurrentAssignment::For(node)) => { self.add_definition( + self.db, symbol, ForStmtDefinitionNodeRef { iterable: &node.iter, @@ -1275,10 +1295,11 @@ where // 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); + self.add_definition(self.db, symbol, named); } Some(CurrentAssignment::Comprehension { node, first }) => { self.add_definition( + self.db, symbol, ComprehensionDefinitionNodeRef { iterable: &node.iter, @@ -1290,6 +1311,7 @@ where } Some(CurrentAssignment::WithItem { item, is_async }) => { self.add_definition( + self.db, symbol, WithItemDefinitionNodeRef { node: item, @@ -1351,13 +1373,14 @@ where let pre_if = self.flow_snapshot(); let constraint = self.record_expression_constraint(test); self.visit_expr(body); - let visibility_constraint = self.record_visibility_constraint(constraint); + let visibility_constraint = + self.create_and_record_visibility_constraint(self.db, constraint); let post_body = self.flow_snapshot(); self.flow_restore(pre_if.clone()); self.record_negated_constraint(constraint); self.visit_expr(orelse); - self.record_negated_visibility_constraint(visibility_constraint); + self.record_negated_visibility_constraint(self.db, visibility_constraint); self.flow_merge(post_body); self.simplify_visibility_constraints(pre_if); } @@ -1425,7 +1448,7 @@ where self.visit_expr(value); for vid in &visibility_constraints { - self.record_visibility_constraint_id(*vid); + self.record_visibility_constraint(self.db, *vid); } // For the last value, we don't need to model control flow. There is short-circuiting @@ -1436,8 +1459,8 @@ where BoolOp::And => self.add_constraint(constraint), BoolOp::Or => self.add_negated_constraint(constraint), }; - let visibility_constraint = self - .add_visibility_constraint(VisibilityConstraint::VisibleIf(constraint)); + let visibility_constraint = + VisibilityConstraint::visible_if(self.db, constraint); let after_expr = self.flow_snapshot(); @@ -1446,9 +1469,9 @@ where // we record all previously existing visibility constraints, and negate the // one for the current expression. for vid in &visibility_constraints { - self.record_visibility_constraint_id(*vid); + self.record_visibility_constraint(*vid); } - self.record_negated_visibility_constraint(visibility_constraint); + self.record_negated_visibility_constraint(self.db, visibility_constraint); snapshots.push(self.flow_snapshot()); // Then we model the non-short-circuiting behavior. Here, we need to delay @@ -1501,6 +1524,7 @@ where let symbol = self.add_symbol(name.id().clone()); let state = self.current_match_case.as_ref().unwrap(); self.add_definition( + self.db, symbol, MatchPatternDefinitionNodeRef { pattern: state.pattern, @@ -1522,6 +1546,7 @@ where let symbol = self.add_symbol(name.id().clone()); let state = self.current_match_case.as_ref().unwrap(); self.add_definition( + self.db, symbol, MatchPatternDefinitionNodeRef { pattern: state.pattern, diff --git a/crates/red_knot_python_semantic/src/semantic_index/builder/except_handlers.rs b/crates/red_knot_python_semantic/src/semantic_index/builder/except_handlers.rs index 6438d1996f3d1..5f286a7cbc657 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder/except_handlers.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder/except_handlers.rs @@ -4,9 +4,9 @@ use super::SemanticIndexBuilder; /// An abstraction over the fact that each scope should have its own [`TryNodeContextStack`] #[derive(Debug, Default)] -pub(super) struct TryNodeContextStackManager(Vec); +pub(super) struct TryNodeContextStackManager<'db>(Vec>); -impl TryNodeContextStackManager { +impl<'db> TryNodeContextStackManager<'db> { /// Push a new [`TryNodeContextStack`] onto the stack of stacks. /// /// Each [`TryNodeContextStack`] is only valid for a single scope @@ -46,7 +46,7 @@ impl TryNodeContextStackManager { } /// Retrieve the [`TryNodeContextStack`] that is relevant for the current scope. - fn current_try_context_stack(&mut self) -> &mut TryNodeContextStack { + fn current_try_context_stack(&'db mut self) -> &'db mut TryNodeContextStack<'db> { self.0 .last_mut() .expect("There should always be at least one `TryBlockContexts` on the stack") @@ -55,9 +55,9 @@ impl TryNodeContextStackManager { /// The contexts of nested `try`/`except` blocks for a single scope #[derive(Debug, Default)] -struct TryNodeContextStack(Vec); +struct TryNodeContextStack<'db>(Vec>); -impl TryNodeContextStack { +impl<'db> TryNodeContextStack<'db> { /// Push a new [`TryNodeContext`] for recording intermediate states /// while visiting a [`ruff_python_ast::StmtTry`] node that has a `finally` branch. fn push_context(&mut self) { @@ -90,11 +90,11 @@ impl TryNodeContextStack { /// It will likely be necessary to add more fields to this struct in the future /// when we add more advanced handling of `finally` branches. #[derive(Debug, Default)] -struct TryNodeContext { - try_suite_snapshots: Vec, +struct TryNodeContext<'db> { + try_suite_snapshots: Vec>, } -impl TryNodeContext { +impl<'db> TryNodeContext<'db> { /// Take a record of what the internal state looked like after a definition fn record_definition(&mut self, snapshot: FlowSnapshot) { self.try_suite_snapshots.push(snapshot); diff --git a/crates/red_knot_python_semantic/src/semantic_index/constraint.rs b/crates/red_knot_python_semantic/src/semantic_index/constraint.rs index 3e7a8fc66856c..b1861c0c6f5ef 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/constraint.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/constraint.rs @@ -5,13 +5,13 @@ use crate::db::Db; use crate::semantic_index::expression::Expression; use crate::semantic_index::symbol::{FileScopeId, ScopeId}; -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub(crate) struct Constraint<'db> { pub(crate) node: ConstraintNode<'db>, pub(crate) is_positive: bool, } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub(crate) enum ConstraintNode<'db> { Expression(Expression<'db>), Pattern(PatternConstraint<'db>), diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs index a455c4b8a85c8..e0444d75bda09 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def.rs @@ -221,15 +221,16 @@ //! snapshot, and merging a snapshot into the current state. The logic using these methods lives in //! [`SemanticIndexBuilder`](crate::semantic_index::builder::SemanticIndexBuilder), e.g. where it //! visits a `StmtIf` node. +pub(crate) use self::symbol_state::ScopedConstraintId; use self::symbol_state::{ BindingIdWithConstraintsIterator, ConstraintIdIterator, DeclarationIdIterator, ScopedDefinitionId, SymbolBindings, SymbolDeclarations, SymbolState, }; -pub(crate) use self::symbol_state::{ScopedConstraintId, ScopedVisibilityConstraintId}; use crate::semantic_index::ast_ids::ScopedUseId; use crate::semantic_index::definition::Definition; use crate::semantic_index::symbol::ScopedSymbolId; -use crate::visibility_constraints::{VisibilityConstraint, VisibilityConstraints}; +use crate::visibility_constraints::VisibilityConstraint; +use crate::Db; use ruff_index::IndexVec; use rustc_hash::FxHashMap; @@ -249,11 +250,8 @@ pub(crate) struct UseDefMap<'db> { /// Array of [`Constraint`] in this scope. all_constraints: AllConstraints<'db>, - /// Array of [`VisibilityConstraint`]s in this scope. - visibility_constraints: VisibilityConstraints<'db>, - /// [`SymbolBindings`] reaching a [`ScopedUseId`]. - bindings_by_use: IndexVec, + bindings_by_use: IndexVec>, /// [`SymbolBindings`] or [`SymbolDeclarations`] reaching a given [`Definition`]. /// @@ -267,10 +265,10 @@ pub(crate) struct UseDefMap<'db> { /// 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. - definitions_by_definition: FxHashMap, SymbolDefinitions>, + definitions_by_definition: FxHashMap, SymbolDefinitions<'db>>, /// [`SymbolState`] visible at end of scope for each symbol. - public_symbols: IndexVec, + public_symbols: IndexVec>, } impl<'db> UseDefMap<'db> { @@ -282,9 +280,9 @@ impl<'db> UseDefMap<'db> { } pub(crate) fn public_bindings( - &self, + &'db self, symbol: ScopedSymbolId, - ) -> BindingWithConstraintsIterator<'_, 'db> { + ) -> BindingWithConstraintsIterator<'map, 'db> { self.bindings_iterator(self.public_symbols[symbol].bindings()) } @@ -323,12 +321,11 @@ impl<'db> UseDefMap<'db> { fn bindings_iterator<'map>( &'map self, - bindings: &'map SymbolBindings, + bindings: &'map SymbolBindings<'db>, ) -> BindingWithConstraintsIterator<'map, 'db> { BindingWithConstraintsIterator { all_definitions: &self.all_definitions, all_constraints: &self.all_constraints, - all_visibility_constraints: &self.visibility_constraints, inner: bindings.iter(), } } @@ -339,7 +336,6 @@ impl<'db> UseDefMap<'db> { ) -> DeclarationsIterator<'map, 'db> { DeclarationsIterator { all_definitions: &self.all_definitions, - visibility_constraints: &self.visibility_constraints, inner: declarations.iter(), } } @@ -347,17 +343,16 @@ impl<'db> UseDefMap<'db> { /// Either live bindings or live declarations for a symbol. #[derive(Debug, PartialEq, Eq)] -enum SymbolDefinitions { - Bindings(SymbolBindings), - Declarations(SymbolDeclarations), +enum SymbolDefinitions<'db> { + Bindings(SymbolBindings<'db>), + Declarations(SymbolDeclarations<'db>), } #[derive(Debug)] pub(crate) struct BindingWithConstraintsIterator<'map, 'db> { all_definitions: &'map IndexVec>>, all_constraints: &'map AllConstraints<'db>, - all_visibility_constraints: &'map VisibilityConstraints<'db>, - inner: BindingIdWithConstraintsIterator<'map>, + inner: BindingIdWithConstraintsIterator<'map, 'db>, } impl<'map, 'db> Iterator for BindingWithConstraintsIterator<'map, 'db> { @@ -374,7 +369,6 @@ impl<'map, 'db> Iterator for BindingWithConstraintsIterator<'map, 'db> { all_constraints, constraint_ids: binding_id_with_constraints.constraint_ids, }, - visibility_constraints: self.all_visibility_constraints, visibility_constraint: binding_id_with_constraints.visibility_constraint, }) } @@ -385,8 +379,7 @@ impl std::iter::FusedIterator for BindingWithConstraintsIterator<'_, '_> {} pub(crate) struct BindingWithConstraints<'map, 'db> { pub(crate) binding: Option>, pub(crate) constraints: ConstraintsIterator<'map, 'db>, - pub(crate) visibility_constraints: &'map VisibilityConstraints<'db>, - pub(crate) visibility_constraint: ScopedVisibilityConstraintId, + pub(crate) visibility_constraint: VisibilityConstraint<'db>, } pub(crate) struct ConstraintsIterator<'map, 'db> { @@ -408,24 +401,21 @@ impl std::iter::FusedIterator for ConstraintsIterator<'_, '_> {} pub(crate) struct DeclarationsIterator<'map, 'db> { all_definitions: &'map IndexVec>>, - visibility_constraints: &'map VisibilityConstraints<'db>, inner: DeclarationIdIterator<'map>, } -pub(crate) struct DeclarationWithConstraint<'map, 'db> { +pub(crate) struct DeclarationWithConstraint<'db> { pub(crate) declaration: Option>, - pub(crate) visibility_constraints: &'map VisibilityConstraints<'db>, - pub(crate) visibility_constraint: ScopedVisibilityConstraintId, + pub(crate) visibility_constraint: VisibilityConstraint<'db>, } impl<'map, 'db> Iterator for DeclarationsIterator<'map, 'db> { - type Item = DeclarationWithConstraint<'map, 'db>; + type Item = DeclarationWithConstraint<'db>; fn next(&mut self) -> Option { self.inner.next().map( |(def_id, visibility_constraint)| DeclarationWithConstraint { declaration: self.all_definitions[def_id], - visibility_constraints: self.visibility_constraints, visibility_constraint, }, ) @@ -436,9 +426,9 @@ 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, - scope_start_visibility: ScopedVisibilityConstraintId, +pub(super) struct FlowSnapshot<'db> { + symbol_states: IndexVec>, + scope_start_visibility: VisibilityConstraint<'db>, } #[derive(Debug)] @@ -449,40 +439,34 @@ pub(super) struct UseDefMapBuilder<'db> { /// Append-only array of [`Constraint`]. all_constraints: AllConstraints<'db>, - /// Append-only array of [`VisibilityConstraint`]. - visibility_constraints: VisibilityConstraints<'db>, - /// A constraint which describes the visibility of the unbound/undeclared state, i.e. /// whether or not the start of the scope is visible. This is important for cases like /// `if True: x = 1; use(x)` where we need to hide the implicit "x = unbound" binding /// in the "else" branch. - scope_start_visibility: ScopedVisibilityConstraintId, + scope_start_visibility: VisibilityConstraint<'db>, /// Live bindings at each so-far-recorded use. - bindings_by_use: IndexVec, + bindings_by_use: IndexVec>, /// Live bindings or declarations for each so-far-recorded definition. - definitions_by_definition: FxHashMap, SymbolDefinitions>, + definitions_by_definition: FxHashMap, SymbolDefinitions<'db>>, /// Currently live bindings and declarations for each symbol. - symbol_states: IndexVec, + symbol_states: IndexVec>, } -impl Default for UseDefMapBuilder<'_> { - fn default() -> Self { +impl<'db> UseDefMapBuilder<'db> { + pub(crate) fn new(db: &'db dyn Db) -> Self { Self { all_definitions: IndexVec::from_iter([None]), all_constraints: IndexVec::new(), - visibility_constraints: VisibilityConstraints::default(), - scope_start_visibility: ScopedVisibilityConstraintId::ALWAYS_TRUE, + scope_start_visibility: VisibilityConstraint::always_true(db), bindings_by_use: IndexVec::new(), definitions_by_definition: FxHashMap::default(), symbol_states: IndexVec::new(), } } -} -impl<'db> UseDefMapBuilder<'db> { pub(super) fn add_symbol(&mut self, symbol: ScopedSymbolId) { let new_symbol = self .symbol_states @@ -490,14 +474,19 @@ impl<'db> UseDefMapBuilder<'db> { debug_assert_eq!(symbol, new_symbol); } - pub(super) fn record_binding(&mut self, symbol: ScopedSymbolId, binding: Definition<'db>) { + pub(super) fn record_binding( + &'db mut self, + db: &'db dyn Db, + symbol: ScopedSymbolId, + binding: Definition<'db>, + ) { let def_id = self.all_definitions.push(Some(binding)); let symbol_state = &mut self.symbol_states[symbol]; self.definitions_by_definition.insert( binding, SymbolDefinitions::Declarations(symbol_state.declarations().clone()), ); - symbol_state.record_binding(def_id); + symbol_state.record_binding(db, def_id); } pub(super) fn add_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId { @@ -511,33 +500,17 @@ impl<'db> UseDefMapBuilder<'db> { } } - pub(super) fn add_visibility_constraint( + pub(super) fn record_visibility_constraint( &mut self, + db: &'db dyn Db, constraint: VisibilityConstraint<'db>, - ) -> ScopedVisibilityConstraintId { - self.visibility_constraints.add(constraint) - } - - pub(super) fn record_visibility_constraint_id( - &mut self, - constraint: ScopedVisibilityConstraintId, ) { for state in &mut self.symbol_states { - state.record_visibility_constraint(&mut self.visibility_constraints, constraint); + state.record_visibility_constraint(db, constraint); } - self.scope_start_visibility = self - .visibility_constraints - .add_and_constraint(self.scope_start_visibility, constraint); - } - - pub(super) fn record_visibility_constraint( - &mut self, - constraint: VisibilityConstraint<'db>, - ) -> ScopedVisibilityConstraintId { - let new_constraint_id = self.add_visibility_constraint(constraint); - self.record_visibility_constraint_id(new_constraint_id); - new_constraint_id + self.scope_start_visibility = + VisibilityConstraint::kleene_and(db, self.scope_start_visibility, constraint); } /// This method resets the visibility constraints for all symbols to a previous state @@ -558,7 +531,7 @@ impl<'db> UseDefMapBuilder<'db> { /// We build a complex visibility constraint for the `y = 0` binding. We build the same /// 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) { + pub(super) fn simplify_visibility_constraints(&mut self, snapshot: FlowSnapshot<'db>) { debug_assert!(self.symbol_states.len() >= snapshot.symbol_states.len()); self.scope_start_visibility = snapshot.scope_start_visibility; @@ -575,7 +548,8 @@ impl<'db> UseDefMapBuilder<'db> { } pub(super) fn record_declaration( - &mut self, + &'db mut self, + db: &'db dyn Db, symbol: ScopedSymbolId, declaration: Definition<'db>, ) { @@ -585,22 +559,23 @@ impl<'db> UseDefMapBuilder<'db> { declaration, SymbolDefinitions::Bindings(symbol_state.bindings().clone()), ); - symbol_state.record_declaration(def_id); + symbol_state.record_declaration(db, def_id); } pub(super) fn record_declaration_and_binding( - &mut self, + &'db mut self, + db: &'db dyn Db, symbol: ScopedSymbolId, definition: Definition<'db>, ) { // We don't need to store anything in self.definitions_by_definition. 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); + symbol_state.record_declaration(db, def_id); + symbol_state.record_binding(db, def_id); } - pub(super) fn record_use(&mut self, symbol: ScopedSymbolId, use_id: ScopedUseId) { + pub(super) fn record_use(&'db mut self, symbol: ScopedSymbolId, use_id: ScopedUseId) { // We have a use of a symbol; clone the current bindings for that symbol, and record them // as the live bindings for this use. let new_use = self @@ -610,7 +585,7 @@ impl<'db> UseDefMapBuilder<'db> { } /// Take a snapshot of the current visible-symbols state. - pub(super) fn snapshot(&self) -> FlowSnapshot { + pub(super) fn snapshot(&'db self) -> FlowSnapshot<'db> { FlowSnapshot { symbol_states: self.symbol_states.clone(), scope_start_visibility: self.scope_start_visibility, @@ -618,7 +593,7 @@ impl<'db> UseDefMapBuilder<'db> { } /// Restore the current builder symbols state to the given snapshot. - pub(super) fn restore(&mut self, snapshot: FlowSnapshot) { + pub(super) fn restore(&mut self, snapshot: FlowSnapshot<'db>) { // 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. @@ -641,7 +616,7 @@ impl<'db> UseDefMapBuilder<'db> { /// 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 /// prior state and the snapshot. - pub(super) fn merge(&mut self, snapshot: FlowSnapshot) { + pub(super) fn merge(&mut self, db: &'db dyn Db, snapshot: FlowSnapshot<'db>) { // 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. @@ -650,19 +625,18 @@ impl<'db> UseDefMapBuilder<'db> { let mut snapshot_definitions_iter = snapshot.symbol_states.into_iter(); for current in &mut self.symbol_states { if let Some(snapshot) = snapshot_definitions_iter.next() { - current.merge(snapshot, &mut self.visibility_constraints); + current.merge(db, snapshot); } else { - current.merge( - SymbolState::undefined(snapshot.scope_start_visibility), - &mut self.visibility_constraints, - ); + current.merge(db, SymbolState::undefined(snapshot.scope_start_visibility)); // Symbol not present in snapshot, so it's unbound/undeclared from that path. } } - self.scope_start_visibility = self - .visibility_constraints - .add_or_constraint(self.scope_start_visibility, snapshot.scope_start_visibility); + self.scope_start_visibility = VisibilityConstraint::kleene_or( + db, + self.scope_start_visibility, + snapshot.scope_start_visibility, + ); } pub(super) fn finish(mut self) -> UseDefMap<'db> { @@ -675,7 +649,6 @@ impl<'db> UseDefMapBuilder<'db> { UseDefMap { all_definitions: self.all_definitions, all_constraints: self.all_constraints, - visibility_constraints: self.visibility_constraints, bindings_by_use: self.bindings_by_use, public_symbols: self.symbol_states, definitions_by_definition: self.definitions_by_definition, diff --git a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs index c6c2d2deac506..a96c7697d00d2 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs @@ -43,7 +43,7 @@ //! //! Tracking live declarations is simpler, since constraints are not involved, but otherwise very //! similar to tracking live bindings. -use crate::semantic_index::use_def::VisibilityConstraints; +use crate::{visibility_constraints::VisibilityConstraint, Db}; use super::bitset::{BitSet, BitSetIterator}; use ruff_index::newtype_index; @@ -94,34 +94,28 @@ type ConstraintsIterator<'a> = std::slice::Iter<'a, Constraints>; type ConstraintsIntoIterator = smallvec::IntoIter; /// Similar to what we have above, but for visibility constraints. -#[newtype_index] -pub(crate) struct ScopedVisibilityConstraintId; - -impl ScopedVisibilityConstraintId { - pub(crate) const ALWAYS_TRUE: ScopedVisibilityConstraintId = - ScopedVisibilityConstraintId::from_u32(0); -} const INLINE_VISIBILITY_CONSTRAINTS: usize = 4; -type InlineVisibilityConstraintsArray = - [ScopedVisibilityConstraintId; INLINE_VISIBILITY_CONSTRAINTS]; -type VisibilityConstraintPerDeclaration = SmallVec; -type VisibilityConstraintPerBinding = SmallVec; -type VisibilityConstraintsIterator<'a> = std::slice::Iter<'a, ScopedVisibilityConstraintId>; -type VisibilityConstraintsIntoIterator = smallvec::IntoIter; +type InlineVisibilityConstraintsArray<'db> = + [VisibilityConstraint<'db>; INLINE_VISIBILITY_CONSTRAINTS]; +type VisibilityConstraintPerDeclaration<'db> = SmallVec>; +type VisibilityConstraintPerBinding<'db> = SmallVec>; +type VisibilityConstraintsIterator<'db> = std::slice::Iter<'db, VisibilityConstraint<'db>>; +type VisibilityConstraintsIntoIterator<'db> = + smallvec::IntoIter>; /// Live declarations for a single symbol at some point in control flow. #[derive(Clone, Debug, PartialEq, Eq)] -pub(super) struct SymbolDeclarations { +pub(super) struct SymbolDeclarations<'db> { /// [`BitSet`]: which declarations (as [`ScopedDefinitionId`]) can reach the current location? pub(crate) live_declarations: Declarations, /// For each live declaration, which visibility constraints apply to it? - pub(crate) visibility_constraints: VisibilityConstraintPerDeclaration, + pub(crate) visibility_constraints: VisibilityConstraintPerDeclaration<'db>, } -impl SymbolDeclarations { - fn undeclared(scope_start_visibility: ScopedVisibilityConstraintId) -> Self { +impl<'db> SymbolDeclarations<'db> { + fn undeclared(scope_start_visibility: VisibilityConstraint<'db>) -> Self { Self { live_declarations: Declarations::with(0), visibility_constraints: VisibilityConstraintPerDeclaration::from_iter([ @@ -131,22 +125,22 @@ impl SymbolDeclarations { } /// Record a newly-encountered declaration for this symbol. - fn record_declaration(&mut self, declaration_id: ScopedDefinitionId) { + fn record_declaration(&mut self, db: &'db dyn Db, declaration_id: ScopedDefinitionId) { self.live_declarations = Declarations::with(declaration_id.into()); self.visibility_constraints = VisibilityConstraintPerDeclaration::with_capacity(1); self.visibility_constraints - .push(ScopedVisibilityConstraintId::ALWAYS_TRUE); + .push(VisibilityConstraint::always_true(db)); } /// Add given visibility constraint to all live bindings. pub(super) fn record_visibility_constraint( &mut self, - visibility_constraints: &mut VisibilityConstraints, - constraint: ScopedVisibilityConstraintId, + db: &'db dyn Db, + constraint: VisibilityConstraint<'db>, ) { for existing in &mut self.visibility_constraints { - *existing = visibility_constraints.add_and_constraint(*existing, constraint); + *existing = VisibilityConstraint::kleene_and(db, *existing, constraint); } } @@ -161,7 +155,7 @@ impl SymbolDeclarations { /// Live bindings and narrowing constraints for a single symbol at some point in control flow. #[derive(Clone, Debug, PartialEq, Eq)] -pub(super) struct SymbolBindings { +pub(super) struct SymbolBindings<'db> { /// [`BitSet`]: which bindings (as [`ScopedDefinitionId`]) can reach the current location? live_bindings: Bindings, @@ -172,11 +166,11 @@ pub(super) struct SymbolBindings { constraints: ConstraintsPerBinding, /// For each live binding, which visibility constraints apply to it? - visibility_constraints: VisibilityConstraintPerBinding, + visibility_constraints: VisibilityConstraintPerBinding<'db>, } -impl SymbolBindings { - fn unbound(scope_start_visibility: ScopedVisibilityConstraintId) -> Self { +impl<'db> SymbolBindings<'db> { + fn unbound(scope_start_visibility: VisibilityConstraint<'db>) -> Self { Self { live_bindings: Bindings::with(0), constraints: ConstraintsPerBinding::from_iter([Constraints::default()]), @@ -187,7 +181,7 @@ impl SymbolBindings { } /// Record a newly-encountered binding for this symbol. - pub(super) fn record_binding(&mut self, binding_id: ScopedDefinitionId) { + pub(super) fn record_binding(&mut self, db: &'db dyn Db, binding_id: ScopedDefinitionId) { // The new binding replaces all previous live bindings in this path, and has no // constraints. self.live_bindings = Bindings::with(binding_id.into()); @@ -196,7 +190,7 @@ impl SymbolBindings { self.visibility_constraints = VisibilityConstraintPerBinding::with_capacity(1); self.visibility_constraints - .push(ScopedVisibilityConstraintId::ALWAYS_TRUE); + .push(VisibilityConstraint::always_true(db)); } /// Add given constraint to all live bindings. @@ -209,11 +203,11 @@ impl SymbolBindings { /// Add given visibility constraint to all live bindings. pub(super) fn record_visibility_constraint( &mut self, - visibility_constraints: &mut VisibilityConstraints, - constraint: ScopedVisibilityConstraintId, + db: &'db dyn Db, + constraint: VisibilityConstraint<'db>, ) { for existing in &mut self.visibility_constraints { - *existing = visibility_constraints.add_and_constraint(*existing, constraint); + *existing = VisibilityConstraint::kleene_and(db, *existing, constraint); } } @@ -228,14 +222,14 @@ impl SymbolBindings { } #[derive(Clone, Debug, PartialEq, Eq)] -pub(super) struct SymbolState { - declarations: SymbolDeclarations, - bindings: SymbolBindings, +pub(super) struct SymbolState<'db> { + declarations: SymbolDeclarations<'db>, + bindings: SymbolBindings<'db>, } -impl SymbolState { +impl<'db> SymbolState<'db> { /// Return a new [`SymbolState`] representing an unbound, undeclared symbol. - pub(super) fn undefined(scope_start_visibility: ScopedVisibilityConstraintId) -> Self { + pub(super) fn undefined(scope_start_visibility: VisibilityConstraint<'db>) -> Self { Self { declarations: SymbolDeclarations::undeclared(scope_start_visibility), bindings: SymbolBindings::unbound(scope_start_visibility), @@ -243,9 +237,9 @@ impl SymbolState { } /// Record a newly-encountered binding for this symbol. - pub(super) fn record_binding(&mut self, binding_id: ScopedDefinitionId) { + pub(super) fn record_binding(&mut self, db: &'db dyn Db, binding_id: ScopedDefinitionId) { debug_assert_ne!(binding_id, ScopedDefinitionId::UNBOUND); - self.bindings.record_binding(binding_id); + self.bindings.record_binding(db, binding_id); } /// Add given constraint to all live bindings. @@ -256,16 +250,15 @@ impl SymbolState { /// Add given visibility constraint to all live bindings. pub(super) fn record_visibility_constraint( &mut self, - visibility_constraints: &mut VisibilityConstraints, - constraint: ScopedVisibilityConstraintId, + db: &'db dyn Db, + constraint: VisibilityConstraint<'db>, ) { - self.bindings - .record_visibility_constraint(visibility_constraints, constraint); + self.bindings.record_visibility_constraint(db, constraint); self.declarations - .record_visibility_constraint(visibility_constraints, constraint); + .record_visibility_constraint(db, constraint); } - pub(super) fn simplify_visibility_constraints(&mut self, snapshot_state: SymbolState) { + pub(super) fn simplify_visibility_constraints(&mut self, snapshot_state: SymbolState<'db>) { if self.bindings.live_bindings == snapshot_state.bindings.live_bindings { self.bindings.visibility_constraints = snapshot_state.bindings.visibility_constraints; } @@ -276,16 +269,16 @@ impl SymbolState { } /// Record a newly-encountered declaration of this symbol. - pub(super) fn record_declaration(&mut self, declaration_id: ScopedDefinitionId) { - self.declarations.record_declaration(declaration_id); + pub(super) fn record_declaration( + &mut self, + db: &'db dyn Db, + declaration_id: ScopedDefinitionId, + ) { + self.declarations.record_declaration(db, declaration_id); } /// Merge another [`SymbolState`] into this one. - pub(super) fn merge( - &mut self, - b: SymbolState, - visibility_constraints: &mut VisibilityConstraints, - ) { + pub(super) fn merge(&mut self, db: &'db dyn Db, b: SymbolState<'db>) { let mut a = Self { bindings: SymbolBindings { live_bindings: Bindings::default(), @@ -395,8 +388,7 @@ impl SymbolState { .next() .expect("visibility_constraints length mismatch"); let current = self.bindings.visibility_constraints.last_mut().unwrap(); - *current = - visibility_constraints.add_or_constraint(*current, a_vis_constraint); + *current = VisibilityConstraint::kleene_or(db, *current, a_vis_constraint); opt_a_def = a_defs_iter.next(); opt_b_def = b_defs_iter.next(); @@ -466,8 +458,7 @@ impl SymbolState { .next() .expect("declarations and visibility_constraints length mismatch"); let current = self.declarations.visibility_constraints.last_mut().unwrap(); - *current = - visibility_constraints.add_or_constraint(*current, a_vis_constraint); + *current = VisibilityConstraint::kleene_or(db, *current, a_vis_constraint); opt_a_decl = a_decls_iter.next(); opt_b_decl = b_decls_iter.next(); @@ -486,11 +477,11 @@ impl SymbolState { } } - pub(super) fn bindings(&self) -> &SymbolBindings { + pub(super) fn bindings(&'db self) -> &'db SymbolBindings<'db> { &self.bindings } - pub(super) fn declarations(&self) -> &SymbolDeclarations { + pub(super) fn declarations(&'db self) -> &'db SymbolDeclarations<'db> { &self.declarations } } @@ -498,21 +489,21 @@ impl SymbolState { /// A single binding (as [`ScopedDefinitionId`]) with an iterator of its applicable /// [`ScopedConstraintId`]. #[derive(Debug)] -pub(super) struct BindingIdWithConstraints<'map> { +pub(super) struct BindingIdWithConstraints<'map, 'db> { pub(super) definition: ScopedDefinitionId, pub(super) constraint_ids: ConstraintIdIterator<'map>, - pub(super) visibility_constraint: ScopedVisibilityConstraintId, + pub(super) visibility_constraint: VisibilityConstraint<'db>, } #[derive(Debug)] -pub(super) struct BindingIdWithConstraintsIterator<'map> { +pub(super) struct BindingIdWithConstraintsIterator<'map, 'db> { definitions: BindingsIterator<'map>, constraints: ConstraintsIterator<'map>, - visibility_constraints: VisibilityConstraintsIterator<'map>, + visibility_constraints: VisibilityConstraintsIterator<'db>, } -impl<'map> Iterator for BindingIdWithConstraintsIterator<'map> { - type Item = BindingIdWithConstraints<'map>; +impl<'map, 'db> Iterator for BindingIdWithConstraintsIterator<'map, 'db> { + type Item = BindingIdWithConstraints<'map, 'db>; fn next(&mut self) -> Option { match ( @@ -536,7 +527,7 @@ impl<'map> Iterator for BindingIdWithConstraintsIterator<'map> { } } -impl std::iter::FusedIterator for BindingIdWithConstraintsIterator<'_> {} +impl std::iter::FusedIterator for BindingIdWithConstraintsIterator<'_, '_> {} #[derive(Debug)] pub(super) struct ConstraintIdIterator<'a> { @@ -558,8 +549,8 @@ pub(super) struct DeclarationIdIterator<'map> { pub(crate) visibility_constraints: VisibilityConstraintsIterator<'map>, } -impl Iterator for DeclarationIdIterator<'_> { - type Item = (ScopedDefinitionId, ScopedVisibilityConstraintId); +impl<'db> Iterator for DeclarationIdIterator<'db> { + type Item = (ScopedDefinitionId, VisibilityConstraint<'db>); fn next(&mut self) -> Option { match (self.declarations.next(), self.visibility_constraints.next()) { @@ -576,169 +567,169 @@ impl Iterator for DeclarationIdIterator<'_> { impl std::iter::FusedIterator for DeclarationIdIterator<'_> {} -#[cfg(test)] -mod tests { - use super::*; - - #[track_caller] - fn assert_bindings(symbol: &SymbolState, expected: &[&str]) { - let actual = symbol - .bindings() - .iter() - .map(|def_id_with_constraints| { - let def_id = def_id_with_constraints.definition; - let def = if def_id == ScopedDefinitionId::UNBOUND { - "unbound".into() - } else { - def_id.as_u32().to_string() - }; - let constraints = def_id_with_constraints - .constraint_ids - .map(ScopedConstraintId::as_u32) - .map(|idx| idx.to_string()) - .collect::>() - .join(", "); - format!("{def}<{constraints}>") - }) - .collect::>(); - assert_eq!(actual, expected); - } - - #[track_caller] - pub(crate) fn assert_declarations(symbol: &SymbolState, expected: &[&str]) { - let actual = symbol - .declarations() - .iter() - .map(|(def_id, _)| { - if def_id == ScopedDefinitionId::UNBOUND { - "undeclared".into() - } else { - def_id.as_u32().to_string() - } - }) - .collect::>(); - assert_eq!(actual, expected); - } - - #[test] - fn unbound() { - let sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); - - assert_bindings(&sym, &["unbound<>"]); - } - - #[test] - fn with() { - let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); - sym.record_binding(ScopedDefinitionId::from_u32(1)); - - assert_bindings(&sym, &["1<>"]); - } - - #[test] - fn record_constraint() { - let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); - sym.record_binding(ScopedDefinitionId::from_u32(1)); - sym.record_constraint(ScopedConstraintId::from_u32(0)); - - assert_bindings(&sym, &["1<0>"]); - } - - #[test] - fn merge() { - let mut visibility_constraints = VisibilityConstraints::default(); - - // merging the same definition with the same constraint keeps the constraint - let mut sym1a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); - sym1a.record_binding(ScopedDefinitionId::from_u32(1)); - sym1a.record_constraint(ScopedConstraintId::from_u32(0)); - - let mut sym1b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); - sym1b.record_binding(ScopedDefinitionId::from_u32(1)); - sym1b.record_constraint(ScopedConstraintId::from_u32(0)); +// #[cfg(test)] +// mod tests { +// use super::*; + +// #[track_caller] +// fn assert_bindings(symbol: &SymbolState, expected: &[&str]) { +// let actual = symbol +// .bindings() +// .iter() +// .map(|def_id_with_constraints| { +// let def_id = def_id_with_constraints.definition; +// let def = if def_id == ScopedDefinitionId::UNBOUND { +// "unbound".into() +// } else { +// def_id.as_u32().to_string() +// }; +// let constraints = def_id_with_constraints +// .constraint_ids +// .map(ScopedConstraintId::as_u32) +// .map(|idx| idx.to_string()) +// .collect::>() +// .join(", "); +// format!("{def}<{constraints}>") +// }) +// .collect::>(); +// assert_eq!(actual, expected); +// } + +// #[track_caller] +// pub(crate) fn assert_declarations(symbol: &SymbolState, expected: &[&str]) { +// let actual = symbol +// .declarations() +// .iter() +// .map(|(def_id, _)| { +// if def_id == ScopedDefinitionId::UNBOUND { +// "undeclared".into() +// } else { +// def_id.as_u32().to_string() +// } +// }) +// .collect::>(); +// assert_eq!(actual, expected); +// } + +// #[test] +// fn unbound() { +// let sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + +// assert_bindings(&sym, &["unbound<>"]); +// } + +// #[test] +// fn with() { +// let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); +// sym.record_binding(ScopedDefinitionId::from_u32(1)); + +// assert_bindings(&sym, &["1<>"]); +// } + +// #[test] +// fn record_constraint() { +// let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); +// sym.record_binding(ScopedDefinitionId::from_u32(1)); +// sym.record_constraint(ScopedConstraintId::from_u32(0)); + +// assert_bindings(&sym, &["1<0>"]); +// } + +// #[test] +// fn merge() { +// let mut visibility_constraints = VisibilityConstraints::default(); + +// // merging the same definition with the same constraint keeps the constraint +// let mut sym1a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); +// sym1a.record_binding(ScopedDefinitionId::from_u32(1)); +// sym1a.record_constraint(ScopedConstraintId::from_u32(0)); + +// let mut sym1b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); +// sym1b.record_binding(ScopedDefinitionId::from_u32(1)); +// sym1b.record_constraint(ScopedConstraintId::from_u32(0)); + +// sym1a.merge(sym1b, &mut visibility_constraints); +// let mut sym1 = sym1a; +// assert_bindings(&sym1, &["1<0>"]); + +// // merging the same definition with differing constraints drops all constraints +// let mut sym2a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); +// sym2a.record_binding(ScopedDefinitionId::from_u32(2)); +// sym2a.record_constraint(ScopedConstraintId::from_u32(1)); + +// let mut sym1b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); +// sym1b.record_binding(ScopedDefinitionId::from_u32(2)); +// sym1b.record_constraint(ScopedConstraintId::from_u32(2)); + +// sym2a.merge(sym1b, &mut visibility_constraints); +// let sym2 = sym2a; +// assert_bindings(&sym2, &["2<>"]); + +// // merging a constrained definition with unbound keeps both +// let mut sym3a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); +// sym3a.record_binding(ScopedDefinitionId::from_u32(3)); +// sym3a.record_constraint(ScopedConstraintId::from_u32(3)); + +// let sym2b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + +// sym3a.merge(sym2b, &mut visibility_constraints); +// let sym3 = sym3a; +// assert_bindings(&sym3, &["unbound<>", "3<3>"]); + +// // merging different definitions keeps them each with their existing constraints +// sym1.merge(sym3, &mut visibility_constraints); +// let sym = sym1; +// assert_bindings(&sym, &["unbound<>", "1<0>", "3<3>"]); +// } + +// #[test] +// fn no_declaration() { +// let sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); + +// assert_declarations(&sym, &["undeclared"]); +// } + +// #[test] +// fn record_declaration() { +// let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); +// sym.record_declaration(ScopedDefinitionId::from_u32(1)); + +// assert_declarations(&sym, &["1"]); +// } + +// #[test] +// fn record_declaration_override() { +// let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); +// sym.record_declaration(ScopedDefinitionId::from_u32(1)); +// sym.record_declaration(ScopedDefinitionId::from_u32(2)); + +// assert_declarations(&sym, &["2"]); +// } + +// #[test] +// fn record_declaration_merge() { +// let mut visibility_constraints = VisibilityConstraints::default(); +// let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); +// sym.record_declaration(ScopedDefinitionId::from_u32(1)); - sym1a.merge(sym1b, &mut visibility_constraints); - let mut sym1 = sym1a; - assert_bindings(&sym1, &["1<0>"]); +// let mut sym2 = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); +// sym2.record_declaration(ScopedDefinitionId::from_u32(2)); - // merging the same definition with differing constraints drops all constraints - let mut sym2a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); - sym2a.record_binding(ScopedDefinitionId::from_u32(2)); - sym2a.record_constraint(ScopedConstraintId::from_u32(1)); +// sym.merge(sym2, &mut visibility_constraints); - let mut sym1b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); - sym1b.record_binding(ScopedDefinitionId::from_u32(2)); - sym1b.record_constraint(ScopedConstraintId::from_u32(2)); +// assert_declarations(&sym, &["1", "2"]); +// } - sym2a.merge(sym1b, &mut visibility_constraints); - let sym2 = sym2a; - assert_bindings(&sym2, &["2<>"]); +// #[test] +// fn record_declaration_merge_partial_undeclared() { +// let mut visibility_constraints = VisibilityConstraints::default(); +// let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); +// sym.record_declaration(ScopedDefinitionId::from_u32(1)); - // merging a constrained definition with unbound keeps both - let mut sym3a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); - sym3a.record_binding(ScopedDefinitionId::from_u32(3)); - sym3a.record_constraint(ScopedConstraintId::from_u32(3)); +// let sym2 = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); - let sym2b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); +// sym.merge(sym2, &mut visibility_constraints); - sym3a.merge(sym2b, &mut visibility_constraints); - let sym3 = sym3a; - assert_bindings(&sym3, &["unbound<>", "3<3>"]); - - // merging different definitions keeps them each with their existing constraints - sym1.merge(sym3, &mut visibility_constraints); - let sym = sym1; - assert_bindings(&sym, &["unbound<>", "1<0>", "3<3>"]); - } - - #[test] - fn no_declaration() { - let sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); - - assert_declarations(&sym, &["undeclared"]); - } - - #[test] - fn record_declaration() { - let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); - sym.record_declaration(ScopedDefinitionId::from_u32(1)); - - assert_declarations(&sym, &["1"]); - } - - #[test] - fn record_declaration_override() { - let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); - sym.record_declaration(ScopedDefinitionId::from_u32(1)); - sym.record_declaration(ScopedDefinitionId::from_u32(2)); - - assert_declarations(&sym, &["2"]); - } - - #[test] - fn record_declaration_merge() { - let mut visibility_constraints = VisibilityConstraints::default(); - let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); - sym.record_declaration(ScopedDefinitionId::from_u32(1)); - - let mut sym2 = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); - sym2.record_declaration(ScopedDefinitionId::from_u32(2)); - - sym.merge(sym2, &mut visibility_constraints); - - assert_declarations(&sym, &["1", "2"]); - } - - #[test] - fn record_declaration_merge_partial_undeclared() { - let mut visibility_constraints = VisibilityConstraints::default(); - let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); - sym.record_declaration(ScopedDefinitionId::from_u32(1)); - - let sym2 = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE); - - sym.merge(sym2, &mut visibility_constraints); - - assert_declarations(&sym, &["undeclared", "1"]); - } -} +// assert_declarations(&sym, &["undeclared", "1"]); +// } +// } diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 7421f2367709b..6e826e6bf5f19 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -279,11 +279,10 @@ fn bindings_ty<'db>( let unbound_visibility = if let Some(BindingWithConstraints { binding: None, constraints: _, - visibility_constraints, visibility_constraint, }) = bindings_with_constraints.peek() { - visibility_constraints.evaluate(db, *visibility_constraint) + crate::visibility_constraints::evaluate(db, *visibility_constraint) } else { Truthiness::AlwaysFalse }; @@ -292,11 +291,11 @@ fn bindings_ty<'db>( |BindingWithConstraints { binding, constraints, - visibility_constraints, visibility_constraint, }| { let binding = binding?; - let static_visibility = visibility_constraints.evaluate(db, visibility_constraint); + let static_visibility = + crate::visibility_constraints::evaluate(db, visibility_constraint); if static_visibility.is_always_false() { return None; @@ -367,11 +366,10 @@ fn declarations_ty<'db>( let undeclared_visibility = if let Some(DeclarationWithConstraint { declaration: None, - visibility_constraints, visibility_constraint, }) = declarations.peek() { - visibility_constraints.evaluate(db, *visibility_constraint) + crate::visibility_constraints::evaluate(db, *visibility_constraint) } else { Truthiness::AlwaysFalse }; @@ -379,11 +377,11 @@ fn declarations_ty<'db>( let mut types = declarations.filter_map( |DeclarationWithConstraint { declaration, - visibility_constraints, visibility_constraint, }| { let declaration = declaration?; - let static_visibility = visibility_constraints.evaluate(db, visibility_constraint); + let static_visibility = + crate::visibility_constraints::evaluate(db, visibility_constraint); if static_visibility.is_always_false() { None diff --git a/crates/red_knot_python_semantic/src/visibility_constraints.rs b/crates/red_knot_python_semantic/src/visibility_constraints.rs index 7c1af890f3bfb..616b1ef3709ea 100644 --- a/crates/red_knot_python_semantic/src/visibility_constraints.rs +++ b/crates/red_knot_python_semantic/src/visibility_constraints.rs @@ -1,6 +1,3 @@ -use ruff_index::IndexVec; - -use crate::semantic_index::ScopedVisibilityConstraintId; use crate::semantic_index::{ ast_ids::HasScopedExpressionId, constraint::{Constraint, ConstraintNode, PatternConstraintKind}, @@ -10,171 +7,155 @@ use crate::Db; const MAX_RECURSION_DEPTH: usize = 10; -#[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) enum VisibilityConstraint<'db> { - AlwaysTrue, - Ambiguous, - VisibleIf(Constraint<'db>), - VisibleIfNot(ScopedVisibilityConstraintId), - KleeneAnd(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), - KleeneOr(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId), -} - -#[derive(Debug, PartialEq, Eq)] -pub(crate) struct VisibilityConstraints<'db> { - constraints: IndexVec>, +#[salsa::interned] +pub(crate) struct VisibilityConstraint<'db> { + kind: VisibilityConstraintKind<'db>, } -impl Default for VisibilityConstraints<'_> { - fn default() -> Self { - Self { - constraints: IndexVec::from_iter([VisibilityConstraint::AlwaysTrue]), - } +impl<'db> VisibilityConstraint<'db> { + pub(crate) fn always_true(db: &'db dyn Db) -> Self { + Self::new(db, VisibilityConstraintKind::AlwaysTrue) } -} -impl<'db> VisibilityConstraints<'db> { - pub(crate) fn add( - &mut self, - constraint: VisibilityConstraint<'db>, - ) -> ScopedVisibilityConstraintId { - self.constraints.push(constraint) + pub(crate) fn ambiguous(db: &'db dyn Db) -> Self { + Self::new(db, VisibilityConstraintKind::Ambiguous) } - pub(crate) fn add_or_constraint( - &mut self, - a: ScopedVisibilityConstraintId, - b: ScopedVisibilityConstraintId, - ) -> ScopedVisibilityConstraintId { - match (&self.constraints[a], &self.constraints[b]) { - (_, VisibilityConstraint::VisibleIfNot(id)) if a == *id => { - ScopedVisibilityConstraintId::ALWAYS_TRUE - } - (VisibilityConstraint::VisibleIfNot(id), _) if *id == b => { - ScopedVisibilityConstraintId::ALWAYS_TRUE - } - _ => self.add(VisibilityConstraint::KleeneOr(a, b)), - } + pub(crate) fn visible_if(db: &'db dyn Db, constraint: Constraint<'db>) -> Self { + Self::new(db, VisibilityConstraintKind::VisibleIf(constraint)) } - pub(crate) fn add_and_constraint( - &mut self, - a: ScopedVisibilityConstraintId, - b: ScopedVisibilityConstraintId, - ) -> ScopedVisibilityConstraintId { - if a == ScopedVisibilityConstraintId::ALWAYS_TRUE { - b - } else if b == ScopedVisibilityConstraintId::ALWAYS_TRUE { - a - } else { - self.add(VisibilityConstraint::KleeneAnd(a, b)) - } + pub(crate) fn visible_if_not(db: &'db dyn Db, constraint: VisibilityConstraint<'db>) -> Self { + Self::new(db, VisibilityConstraintKind::VisibleIfNot(constraint)) } - /// Analyze the statically known visibility for a given visibility constraint. - pub(crate) fn evaluate(&self, db: &'db dyn Db, id: ScopedVisibilityConstraintId) -> Truthiness { - self.evaluate_impl(db, id, MAX_RECURSION_DEPTH) + pub(crate) fn kleene_and( + db: &'db dyn Db, + lhs: VisibilityConstraint<'db>, + rhs: VisibilityConstraint<'db>, + ) -> Self { + Self::new(db, VisibilityConstraintKind::KleeneAnd(lhs, rhs)) } - fn evaluate_impl( - &self, + pub(crate) fn kleene_or( db: &'db dyn Db, - id: ScopedVisibilityConstraintId, - max_depth: usize, - ) -> Truthiness { - if max_depth == 0 { - return Truthiness::Ambiguous; + lhs: VisibilityConstraint<'db>, + rhs: VisibilityConstraint<'db>, + ) -> Self { + Self::new(db, VisibilityConstraintKind::KleeneOr(lhs, rhs)) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) enum VisibilityConstraintKind<'db> { + AlwaysTrue, + Ambiguous, + VisibleIf(Constraint<'db>), + VisibleIfNot(VisibilityConstraint<'db>), + KleeneAnd(VisibilityConstraint<'db>, VisibilityConstraint<'db>), + KleeneOr(VisibilityConstraint<'db>, VisibilityConstraint<'db>), +} + +/// Analyze the statically known visibility for a given visibility constraint. +pub(crate) fn evaluate<'db>(db: &'db dyn Db, constraint: VisibilityConstraint<'db>) -> Truthiness { + evaluate_impl(db, constraint, MAX_RECURSION_DEPTH) +} + +fn evaluate_impl<'db>( + db: &'db dyn Db, + constraint: VisibilityConstraint<'db>, + max_depth: usize, +) -> Truthiness { + if max_depth == 0 { + return Truthiness::Ambiguous; + } + + match constraint.kind(db) { + VisibilityConstraintKind::AlwaysTrue => Truthiness::AlwaysTrue, + VisibilityConstraintKind::Ambiguous => Truthiness::Ambiguous, + VisibilityConstraintKind::VisibleIf(constraint) => analyze_single(db, &constraint), + VisibilityConstraintKind::VisibleIfNot(negated) => { + evaluate_impl(db, negated, max_depth - 1).negate() } + VisibilityConstraintKind::KleeneAnd(lhs, rhs) => { + let lhs = evaluate_impl(db, lhs, max_depth - 1); - let visibility_constraint = &self.constraints[id]; - match visibility_constraint { - VisibilityConstraint::AlwaysTrue => Truthiness::AlwaysTrue, - VisibilityConstraint::Ambiguous => Truthiness::Ambiguous, - VisibilityConstraint::VisibleIf(constraint) => Self::analyze_single(db, constraint), - VisibilityConstraint::VisibleIfNot(negated) => { - self.evaluate_impl(db, *negated, max_depth - 1).negate() + if lhs == Truthiness::AlwaysFalse { + return Truthiness::AlwaysFalse; } - VisibilityConstraint::KleeneAnd(lhs, rhs) => { - let lhs = self.evaluate_impl(db, *lhs, max_depth - 1); - - if lhs == Truthiness::AlwaysFalse { - return Truthiness::AlwaysFalse; - } - let rhs = self.evaluate_impl(db, *rhs, max_depth - 1); + let rhs = evaluate_impl(db, rhs, max_depth - 1); - if rhs == Truthiness::AlwaysFalse { - Truthiness::AlwaysFalse - } else if lhs == Truthiness::AlwaysTrue && rhs == Truthiness::AlwaysTrue { - Truthiness::AlwaysTrue - } else { - Truthiness::Ambiguous - } + if rhs == Truthiness::AlwaysFalse { + Truthiness::AlwaysFalse + } else if lhs == Truthiness::AlwaysTrue && rhs == Truthiness::AlwaysTrue { + Truthiness::AlwaysTrue + } else { + Truthiness::Ambiguous } - VisibilityConstraint::KleeneOr(lhs_id, rhs_id) => { - let lhs = self.evaluate_impl(db, *lhs_id, max_depth - 1); + } + VisibilityConstraintKind::KleeneOr(lhs, rhs) => { + let lhs = evaluate_impl(db, lhs, max_depth - 1); - if lhs == Truthiness::AlwaysTrue { - return Truthiness::AlwaysTrue; - } + if lhs == Truthiness::AlwaysTrue { + return Truthiness::AlwaysTrue; + } - let rhs = self.evaluate_impl(db, *rhs_id, max_depth - 1); + let rhs = evaluate_impl(db, rhs, max_depth - 1); - if rhs == Truthiness::AlwaysTrue { - Truthiness::AlwaysTrue - } else if lhs == Truthiness::AlwaysFalse && rhs == Truthiness::AlwaysFalse { - Truthiness::AlwaysFalse - } else { - Truthiness::Ambiguous - } + if rhs == Truthiness::AlwaysTrue { + Truthiness::AlwaysTrue + } else if lhs == Truthiness::AlwaysFalse && rhs == Truthiness::AlwaysFalse { + Truthiness::AlwaysFalse + } else { + Truthiness::Ambiguous } } } +} - fn analyze_single(db: &dyn Db, constraint: &Constraint) -> Truthiness { - match constraint.node { - ConstraintNode::Expression(test_expr) => { - let inference = infer_expression_types(db, test_expr); - let scope = test_expr.scope(db); - let ty = - inference.expression_ty(test_expr.node_ref(db).scoped_expression_id(db, scope)); +fn analyze_single(db: &dyn Db, constraint: &Constraint) -> Truthiness { + match constraint.node { + ConstraintNode::Expression(test_expr) => { + let inference = infer_expression_types(db, test_expr); + let scope = test_expr.scope(db); + let ty = + inference.expression_ty(test_expr.node_ref(db).scoped_expression_id(db, scope)); - ty.bool(db).negate_if(!constraint.is_positive) - } - ConstraintNode::Pattern(inner) => match inner.kind(db) { - PatternConstraintKind::Value(value, guard) => { - let subject_expression = inner.subject(db); - let inference = infer_expression_types(db, *subject_expression); - let scope = subject_expression.scope(db); - let subject_ty = inference.expression_ty( - subject_expression - .node_ref(db) - .scoped_expression_id(db, scope), - ); - - let inference = infer_expression_types(db, *value); - let scope = value.scope(db); - let value_ty = - inference.expression_ty(value.node_ref(db).scoped_expression_id(db, scope)); - - if subject_ty.is_single_valued(db) { - let truthiness = - Truthiness::from(subject_ty.is_equivalent_to(db, value_ty)); - - if truthiness.is_always_true() && guard.is_some() { - // Fall back to ambiguous, the guard might change the result. - Truthiness::Ambiguous - } else { - truthiness - } - } else { + ty.bool(db).negate_if(!constraint.is_positive) + } + ConstraintNode::Pattern(inner) => match inner.kind(db) { + PatternConstraintKind::Value(value, guard) => { + let subject_expression = inner.subject(db); + let inference = infer_expression_types(db, *subject_expression); + let scope = subject_expression.scope(db); + let subject_ty = inference.expression_ty( + subject_expression + .node_ref(db) + .scoped_expression_id(db, scope), + ); + + let inference = infer_expression_types(db, *value); + let scope = value.scope(db); + let value_ty = + inference.expression_ty(value.node_ref(db).scoped_expression_id(db, scope)); + + if subject_ty.is_single_valued(db) { + let truthiness = Truthiness::from(subject_ty.is_equivalent_to(db, value_ty)); + + if truthiness.is_always_true() && guard.is_some() { + // Fall back to ambiguous, the guard might change the result. Truthiness::Ambiguous + } else { + truthiness } - } - PatternConstraintKind::Singleton(..) | PatternConstraintKind::Unsupported => { + } else { Truthiness::Ambiguous } - }, - } + } + PatternConstraintKind::Singleton(..) | PatternConstraintKind::Unsupported => { + Truthiness::Ambiguous + } + }, } }