mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-31 07:37:38 +00:00

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>
4.2 KiB
4.2 KiB
global
references
Implicit global in function
A name reference to a never-defined symbol in a function is implicitly a global lookup.
x = 1
def f():
reveal_type(x) # revealed: Unknown | Literal[1]
Explicit global in function
x = 1
def f():
global x
reveal_type(x) # revealed: Unknown | Literal[1]
Unassignable type in function
x: int = 1
def f():
y: int = 1
# error: [invalid-assignment] "Object of type `Literal[""]` is not assignable to `int`"
y = ""
global x
# 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
A global
statement causes lookup to skip any bindings in intervening scopes:
x: int = 1
def outer():
x: str = ""
def inner():
global x
reveal_type(x) # revealed: int
Narrowing
An assignment following a global
statement should narrow the type in the local scope after the
assignment.
x: int | None
def f():
global x
x = 1
reveal_type(x) # revealed: Literal[1]
nonlocal
and global
A binding cannot be both nonlocal
and global
. This should emit a semantic syntax error. CPython
marks the nonlocal
line, while mypy
, pyright
, and ruff
(PLE0115
) mark the global
line.
x = 1
def f():
x = 1
def g() -> None:
nonlocal x
global x # TODO: error: [invalid-syntax] "name 'x' is nonlocal and global"
x = None
Global declaration after global
statement
def f():
global x
y = x
x = 1 # No error.
x = 2
Semantic syntax errors
Using a name prior to its global
declaration in the same scope is a syntax error.
def f():
print(x)
global x # error: [invalid-syntax] "name `x` is used prior to global declaration"
print(x)
def f():
global x
print(x)
global x # error: [invalid-syntax] "name `x` is used prior to global declaration"
print(x)
def f():
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)
global x, y # error: [invalid-syntax] "name `x` is used prior to global declaration"
print(x)
def f():
x = 1
global x # error: [invalid-syntax] "name `x` is used prior to global declaration"
x = 1
def f():
global x
x = 1
global x # error: [invalid-syntax] "name `x` is used prior to global declaration"
x = 1
def f():
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
global x, y # error: [invalid-syntax] "name `x` is used prior to global declaration"
del x
def f():
del x
global x # error: [invalid-syntax] "name `x` is used prior to global declaration"
del x
def f():
global x
del x
global x # error: [invalid-syntax] "name `x` is used prior to global declaration"
del x
def f():
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
global x, y # error: [invalid-syntax] "name `x` is used prior to global declaration"
del x
def f():
print(f"{x=}")
global x # error: [invalid-syntax] "name `x` is used prior to global declaration"
# still an error in module scope
x = None
global x # error: [invalid-syntax] "name `x` is used prior to global declaration"
Local bindings override preceding global
bindings
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
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
x: int = 1
def f():
global x
x: str = "foo" # TODO: error: [invalid-syntax] "annotated name 'x' can't be global"