Skip to content

Variable Scoping Rules

No global or nonlocal Keywords

Sharpy does not support Python's global or nonlocal keywords. This aligns with C# scoping semantics:

# ❌ Invalid - these keywords don't exist in Sharpy
global x       # ERROR: unexpected 'global'
nonlocal y     # ERROR: unexpected 'nonlocal'

To modify outer scope variables, use explicit assignment to a mutable container or return values from functions.

Block Scoping

Sharpy uses C#-style block scoping: all compound statement bodies introduce a new scope. Variables declared inside a block are not visible outside it. This is a deliberate departure from Python, where variables leak out of most blocks.

Block-Scoped Compound Statements (variables declared inside don't leak): - if / elif / else bodies - while body - for body (including the loop variable itself) - try body - except body (including the as binding) - else body (in try/except/else) - finally body - with body - Comprehensions (including walrus assignments inside comprehensions)

Note on try/except/else/finally: Variables declared in the try body are not visible in except, else, or finally handlers. If a variable must be accessible across all clauses, declare it before the try statement:

result: int = 0
try:
    result = risky_operation()
except ValueError as e:
    print(f"Failed: {e}")
finally:
    print(f"Result was: {result}")

Containing-Scope Constructs (variable persists): - Declarations in a function body or module top-level (outside any compound statement) - Walrus operator (x := value) in non-block contexts - see Walrus Operator

Walrus Operator Scoping:

The walrus operator (:=) assigns to the containing scope. In most cases this is the enclosing function or module. However, inside block-scoped constructs like comprehensions, the walrus variable is scoped to that block:

# Walrus in if-statement: variable persists in containing scope
if (match := pattern.search(text)) is not None:
    print(match)  # OK
print(match)      # OK - walrus assigned in containing scope

# Walrus in comprehension: variable is comprehension-local
results = [y * 2 for x in items if (y := transform(x)) > 0]
print(y)          # ERROR: 'y' does not exist in this scope

Note: This differs from Python 3.8+, where walrus in comprehensions leaks to the outer scope. In Sharpy, the syntactic boundary equals the semantic boundary—comprehension delimiters ([...], {...}) which fully contain all variables declared within.

Example

x = "outer"

for x in range(5):      # New 'x' shadows outer, block-scoped
    print(x)            # Prints 0, 1, 2, 3, 4

print(x)                # Prints "outer", 'x' was shadowed only
                        # in the for-loop, and not modified.

To modify outer variable

x = 0
for i in range(5):      # 'i' is block-scoped
    x += i              # Modifies outer 'x'
print(x)                # 10
print(i)                # ERROR: 'i' is block-scoped

Assignment Statement

# Simple assignment
x = 10

# Multiple assignment (unpacking)
x, y = 10, 20

# Augmented assignment
x += 5
count *= 2

Variable Shadowing

Variables can be redeclared in the same scope with a different type using explicit type annotation:

x: int = 5              # Initial declaration
x = 10                  # Assignment (same type)
x: str = "hello"        # Shadowing (new type, requires annotation)

# With auto keyword for type inference
x: int = 5
x: auto = "hello"       # Shadowing with inferred type

Implementation: - 🔄 Lowered - Generates variable names (x, x_1_..., x_2_...). The versioned variable names are appended with UUIDs to prevent the user from predicting the internal names and referencing them inadvertently.