Skip to content

Reassigning a for-loop variable widens its type via LUB, blocking runtime-dispatchable code #143

@timfennis

Description

@timfennis

Summary

When a for-loop variable is reassigned to a value of a different type, the analyser computes the LUB of the loop's element type and the new type and widens the binding. This loses the precise type the runtime actually holds and can block valid runtime-dispatchable code under stricter static analysis (or produce confusingly inferred types for downstream code).

This is a specific instance of #122 (flow-sensitive typing) but worth filing on its own because it surfaces in real user programs and the fix may be narrower than the full flow-typing project.

Repro

let lines: Sequence<String> = ["a b c", "d e f"];

for line in lines {
    line = line.split(" ");        // line was String, now List<String>
    let first = line.remove(0);    // error
}

Analyser error:

error[resolver]: No function called 'remove' found that matches the arguments 'Sequence<String>, Int'

At runtime, line after the reassignment really is a List<String>remove(List, Int) would dispatch correctly. But the analyser has widened line to Sequence<String> (LUB(String, List<String>) = Sequence<String>), and the stdlib's remove is declared as (List<Any>, Int), which fails the strict subtype check.

Why this matters

In PR #141 the analyser's compatibility check became stricter — that didn't introduce the widening but it does mean the widened type is now load-bearing in more places. The repro above was an Advent-of-Brian 2025 day 11 solution that used to run; it now fails analysis.

Expected behaviour

After a reassignment that fully replaces the binding's value (i.e. not a compound update), the subsequent uses of the variable should see the new type, not the LUB. The temporal union "`line` is either a String or a List" doesn't correspond to any actual program state — by the time line.remove(0) runs, the reassignment has definitely happened.

Workarounds today

  • Use a different variable name for the split result instead of shadowing the loop var.
  • Cast/widen the call site explicitly.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinginterpreterIssue relates to the interpreter

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions