mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 10:48:32 +00:00
[ty] Implement global
handling and load-before-global-declaration
syntax error (#17637)
Summary -- This PR resolves both the typing-related and syntax error TODOs added in #17563 by tracking a set of `global` bindings for each scope. As discussed below, we avoid the additional AST traversal from ruff by collecting `Name`s from `global` statements while building the semantic index and emit a syntax error if the `Name` is already bound in the current scope at the point of the `global` statement. This has the downside of separating the error from the `SemanticSyntaxChecker`, but I plan to explore using this approach in the `SemanticSyntaxChecker` itself as a follow-up. It seems like this may be a better approach for ruff as well. Test Plan -- Updated all of the related mdtests to remove the TODOs (and add quotes I forgot on the messages). There is one remaining TODO, but it requires `nonlocal` support, which isn't even incorporated into the `SemanticSyntaxChecker` yet. --------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
parent
67cd94ed64
commit
57bf7dfbd9
5 changed files with 174 additions and 48 deletions
|
@ -32,8 +32,14 @@ def f():
|
|||
y = ""
|
||||
|
||||
global x
|
||||
# TODO: error: [invalid-assignment] "Object of type `Literal[""]` is not assignable to `int`"
|
||||
# error: [invalid-assignment] "Object of type `Literal[""]` is not assignable to `int`"
|
||||
x = ""
|
||||
|
||||
global z
|
||||
# error: [invalid-assignment] "Object of type `Literal[""]` is not assignable to `int`"
|
||||
z = ""
|
||||
|
||||
z: int
|
||||
```
|
||||
|
||||
## Nested intervening scope
|
||||
|
@ -48,8 +54,7 @@ def outer():
|
|||
|
||||
def inner():
|
||||
global x
|
||||
# TODO: revealed: int
|
||||
reveal_type(x) # revealed: str
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
## Narrowing
|
||||
|
@ -87,8 +92,7 @@ def f():
|
|||
```py
|
||||
def f():
|
||||
global x
|
||||
# TODO this should also not be an error
|
||||
y = x # error: [unresolved-reference] "Name `x` used when not defined"
|
||||
y = x
|
||||
x = 1 # No error.
|
||||
|
||||
x = 2
|
||||
|
@ -99,79 +103,111 @@ x = 2
|
|||
Using a name prior to its `global` declaration in the same scope is a syntax error.
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
def f():
|
||||
print(x) # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x
|
||||
print(x)
|
||||
global x # error: [invalid-syntax] "name `x` is used prior to global declaration"
|
||||
print(x)
|
||||
|
||||
def f():
|
||||
global x
|
||||
print(x) # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x
|
||||
print(x)
|
||||
global x # error: [invalid-syntax] "name `x` is used prior to global declaration"
|
||||
print(x)
|
||||
|
||||
def f():
|
||||
print(x) # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x, y
|
||||
print(x)
|
||||
global x, y # error: [invalid-syntax] "name `x` is used prior to global declaration"
|
||||
print(x)
|
||||
|
||||
def f():
|
||||
global x, y
|
||||
print(x) # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x, y
|
||||
print(x)
|
||||
global x, y # error: [invalid-syntax] "name `x` is used prior to global declaration"
|
||||
print(x)
|
||||
|
||||
def f():
|
||||
x = 1 # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x
|
||||
x = 1
|
||||
global x # error: [invalid-syntax] "name `x` is used prior to global declaration"
|
||||
x = 1
|
||||
|
||||
def f():
|
||||
global x
|
||||
x = 1 # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x
|
||||
x = 1
|
||||
global x # error: [invalid-syntax] "name `x` is used prior to global declaration"
|
||||
x = 1
|
||||
|
||||
def f():
|
||||
del x # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x, y
|
||||
del x
|
||||
global x, y # error: [invalid-syntax] "name `x` is used prior to global declaration"
|
||||
del x
|
||||
|
||||
def f():
|
||||
global x, y
|
||||
del x # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x, y
|
||||
del x
|
||||
global x, y # error: [invalid-syntax] "name `x` is used prior to global declaration"
|
||||
del x
|
||||
|
||||
def f():
|
||||
del x # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x
|
||||
del x
|
||||
global x # error: [invalid-syntax] "name `x` is used prior to global declaration"
|
||||
del x
|
||||
|
||||
def f():
|
||||
global x
|
||||
del x # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x
|
||||
del x
|
||||
global x # error: [invalid-syntax] "name `x` is used prior to global declaration"
|
||||
del x
|
||||
|
||||
def f():
|
||||
del x # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x, y
|
||||
del x
|
||||
global x, y # error: [invalid-syntax] "name `x` is used prior to global declaration"
|
||||
del x
|
||||
|
||||
def f():
|
||||
global x, y
|
||||
del x # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x, y
|
||||
del x
|
||||
global x, y # error: [invalid-syntax] "name `x` is used prior to global declaration"
|
||||
del x
|
||||
|
||||
def f():
|
||||
print(f"{x=}") # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x
|
||||
print(f"{x=}")
|
||||
global x # error: [invalid-syntax] "name `x` is used prior to global declaration"
|
||||
|
||||
# still an error in module scope
|
||||
x = None # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x
|
||||
x = None
|
||||
global x # error: [invalid-syntax] "name `x` is used prior to global declaration"
|
||||
```
|
||||
|
||||
## Local bindings override preceding `global` bindings
|
||||
|
||||
```py
|
||||
x = 42
|
||||
|
||||
def f():
|
||||
global x
|
||||
reveal_type(x) # revealed: Unknown | Literal[42]
|
||||
x = "56"
|
||||
reveal_type(x) # revealed: Literal["56"]
|
||||
```
|
||||
|
||||
## Local assignment prevents falling back to the outer scope
|
||||
|
||||
```py
|
||||
x = 42
|
||||
|
||||
def f():
|
||||
# error: [unresolved-reference] "Name `x` used when not defined"
|
||||
reveal_type(x) # revealed: Unknown
|
||||
x = "56"
|
||||
reveal_type(x) # revealed: Literal["56"]
|
||||
```
|
||||
|
||||
## Annotating a `global` binding is a syntax error
|
||||
|
||||
```py
|
||||
x: int = 1
|
||||
|
||||
def f():
|
||||
global x
|
||||
x: str = "foo" # TODO: error: [invalid-syntax] "annotated name 'x' can't be global"
|
||||
```
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue