chapel icon indicating copy to clipboard operation
chapel copied to clipboard

"used before defined" error for module-scope variable used in function

Open benharsh opened this issue 2 months ago • 3 comments

The following program produces a "used before defined" error in production:

proc foo() {
  x = 1; // error here
}

var x : int = 0;

proc main() {
  foo();
  writeln(x);
}

There seemed to be a lack of consensus for how this case should be handled amongst a handful of team members, so I'm opening an issue to continue discussion.

benharsh avatar Sep 10 '25 21:09 benharsh

I don't have a strong opinion one way or the other about his this program should behave, but I do think we need to clarify the spec, which is at present ambiguous.

Module level variables can be accessed anywhere within that module after the initialization of that variable.

In this case, it's entirely unclear to me if "after the initialization of that variable" refers to the place where the variable was declared (i.e., it's initialization point) or a moment in time (i.e., the step in the module initialization process during which the variable is initialized). My initial reading was the latter, since I expected something like "initialization point" for place-determined things. As such, the way I see it, the spec is ambiguous, and allows for both emitting an error on line 2, and compiling the program without diagnostics.

DanilaFe avatar Sep 10 '25 21:09 DanilaFe

I'm in favor of having the code in the OP work, but don't think it's worth fixing in production if it's fixed in Dyno. Specifically:

  • Chapel generally supports the theme of letting things be declared in flexible orders within a scope to support mutual recursion and such without introducing forward/prototype declarations in the language
  • Executable code within a scope is a key exception to this because execution (within a scope) is linear by nature
  • As a result declarations like var x are executable whereas declarations like foo() are not (until invoked)
  • For that reason, I think it would be most in the theme of the language to permit foo() to be declared before or after x and before or after it is called

In more detail, I imagine the compiler to proceed in a way like this:

  • define proc foo() — OK, this is declarative code, not executable, so we can file this away for now without understanding everything about it
  • define x — OK, I can see it’s an int and this is the first executable statement we've seen
  • define proc main() — OK, this is declarative code
  • resolve main() — this is the next thing that will happen in execution after initializing all the modules
  • see that it calls foo()
  • resolve foo() — see that it refers to x which is OK because I already know what x is
  • resolve writeln(x) — this is OK because I already know what x is

Module level variables can be accessed anywhere within that module after the initialization of that variable.

I think of this as being temporal rather than positional

bradcray avatar Sep 10 '25 22:09 bradcray

My view was that the description for variables is distinct from that of functions. Functions focuses on the scope and thus defines the bounds of where it can be referred to positionally. Visibility is also defined positionally (which is why we know to error about a use-before-def instead of "can't find"), but the visibility of a variable and its ability to be used is defined by its placement relative to the reference. I don't think temporality plays into it, and if it did, it would make a more confusing situation where

proc foo() {
  x = 1; // error here
  return x;
}

var w = foo();  // but this is what made it occur earlier
var x : int = 0;

proc main() {
  foo();
  writeln(x);
}

is a subtle enough change to adjust the behavior.

lydia-duncan avatar Sep 10 '25 22:09 lydia-duncan