Files
ruff/crates/ty_python_semantic/resources/mdtest/loops/while_loop.md
2026-01-13 11:27:12 -08:00

3.0 KiB

While loops

Basic while loop

def _(flag: bool):
    x = 1
    while flag:
        x = 2

    reveal_type(x)  # revealed: Literal[1, 2]

while with else (no break)

def _(flag: bool):
    x = 1
    while flag:
        x = 2
    else:
        reveal_type(x)  # revealed: Literal[1, 2]
        x = 3

    reveal_type(x)  # revealed: Literal[3]

while with else (may break)

def _(flag: bool, flag2: bool):
    x = 1
    y = 0
    while flag:
        x = 2
        if flag2:
            y = 4
            break
    else:
        y = x
        x = 3

    reveal_type(x)  # revealed: Literal[2, 3]
    reveal_type(y)  # revealed: Literal[4, 1, 2]

Nested while loops

def flag() -> bool:
    return True

x = 1

while flag():
    x = 2

    while flag():
        x = 3
        if flag():
            break
    else:
        x = 4

    if flag():
        break
else:
    x = 5

reveal_type(x)  # revealed: Literal[3, 4, 5]

Boundness

Make sure that the boundness information is correctly tracked in while loop control flow.

Basic while loop

def _(flag: bool):
    while flag:
        x = 1

    # error: [possibly-unresolved-reference]
    x

while with else (no break)

def _(flag: bool):
    while flag:
        y = 1
    else:
        x = 1

    # no error, `x` is always bound
    x
    # error: [possibly-unresolved-reference]
    y

while with else (may break)

def _(flag: bool, flag2: bool):
    while flag:
        x = 1
        if flag2:
            break
    else:
        y = 1

    # error: [possibly-unresolved-reference]
    x
    # error: [possibly-unresolved-reference]
    y

Condition with object that implements __bool__ incorrectly

class NotBoolable:
    __bool__: int = 3

# error: [unsupported-bool-conversion] "Boolean conversion is not supported for type `NotBoolable`"
while NotBoolable():
    ...

Cyclic control flow

def random() -> bool:
    return False

i = 0
reveal_type(i)  # revealed: Literal[0]
while random():
    i += 1
    reveal_type(i)  # revealed: int

A more complex example, where the loop condition narrows both the loop-back value and the end-of-loop value.

x = "A"
while x != "C":
    reveal_type(x) # revealed: Literal["A", "B"]
    if random():
        x = "B"
    else:
        x = "C"
    reveal_type(x) # revealed: Literal["B", "C"]
reveal_type(x) # revealed: Literal["C"]

The same thing, but nested loops.

x = "A"
while x != "E":
    reveal_type(x) # revealed: Literal["A", "B", "D"]
    while x != "C":
        reveal_type(x) # revealed: Literal["A", "B", "D"]
        if random():
            x = "B"
        else:
            x = "C"
        reveal_type(x) # revealed: Literal["B", "C", "D"]
    reveal_type(x) # revealed: Literal["C"]
    if random():
        x = "D"
    else:
        x = "E"
    reveal_type(x) # revealed: Literal["D", "E"]
reveal_type(x) # revealed: Literal["E"]