Referencing local variable before assignment results in unexpected behavior

Theoretically we could have our cake and eat it: Capture-by-value could be considered a subset of capture-by-Core.Box julia. The idea would be that some people opt into a strict-mode – either by linter or by core language support – that turns capture-by-Core.Box into an error. People could opt into strict mode either on a per-file basis or on a per-begin-end-block basis. (one would like to support both ways, for modules that include other files, with varying strictness)

I’m not sure a linter would be able to do this well, because one needs to evaluate code (because of macros and eval and @generated); naively looking at source-code is not enough.

For this, we would need to identify and communicate a reasonable set of sufficient conditions that prevent Core.Box emission, commit to guaranteeing that these won’t emit Boxes, and then check that during lowering (of course, some patterns that are errors in strict-mode might still be compiled into box-free code).

A simple rule could be: A closure / inner function / anon function is valid if and only if every closed over variable is single static assignment (lexically! We don’t have dom-trees during lowering, so x = 1; x = 2; foo() = x; would be invalid, as would be @label jump_here; x = 1; foo() = x;) and this single static assignment “lexically dominates” the closure creation. “Lexically dominates” e.g. means that there is no @label / @goto jumptarget between the assignment and its use; we would need to look at the current implementation (in femtolisp) and identify a communicable rule operating on macro-expanded expression trees (pre-lowered code) that is sufficient.

This strict mode would only restrict valid julia code; it would be guaranteed that code that lowers under strict-mode without errors has identical behavior and emitted machine-code as non-strict code.

1 Like