Skip to content

report of x raises "undefined variable" for a name assigned inside the same unobserved: block, while a plain read of it works #895

Description

@InauguralPhysicist

Finding

A name assigned inside an unobserved: block is readable inside the block and after it, but interrogating it inside the block raises undefined variable. Plain reads and interrogations of the same name disagree about whether it exists.

Reproduction

Found while working #871, at 2148dc2 + that branch (the #871 diff only adds unobserved: depth bookkeeping and cannot affect name resolution, so this is pre-existing).

unobserved:
    m is 9.0
    m is m * 0.5
    print of ("plain read inside: " + (str of m))     # works
print of ("read after block:   " + (str of m))        # works
print of ("report after block: " + (report of m))     # works
plain read inside: 4.5
read after block: 4.5
report after block: equilibrium

Move the interrogation inside the block and the same name is undefined:

unobserved:
    m is 9.0
    print of (report of m)
Error line 3: undefined variable 'm'
     3 |     print of (report of m)
       |                      ^
  at <module> (line 3)

Assigning m before the block makes it work, so it is specific to names whose only assignment is inside the block.

Likely mechanism

AST_UNOBSERVED's compile path (src/compiler.c, the block around the assigned / outside / captured_here / interrogated_here name sets) does escape analysis to decide which names can use the in-place numeric fast path. A name that is assigned inside the block and interrogated inside the block appears not to be routed into the set that keeps it resolvable by the interrogation opcodes — OP_REPORT_NAME then resolves against the env and finds nothing, while the plain-read path uses the slot directly.

Why it matters

The error is loud, which is the good news. But it is loud in a misleading way — "undefined variable" for a variable that is very obviously defined two lines up, and which str of reads happily on the next line. The block is documented as a transparent performance annotation (README.md:189), so a name changing visibility depending on whether you read it or ask about it contradicts that.

This is the same family as #871 (unobserved: changing semantics rather than only speed) but a distinct root: that one is the dynamic depth counter, this one is compile-time name resolution.

Suggested fix

Include interrogated-inside names in whichever set keeps a binding env-resolvable, i.e. treat an interrogation inside the block the same way an interrogation from outside it is already treated.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions