An ugly binding bug

5 views
Skip to first unread message

Bill Cox

unread,
Mar 12, 2023, 6:47:13 PM3/12/23
to Rune language discussion
Consider the following code:

class Foo(self) {
}

func asdf() {
  println bar
}

foo = null(Foo)
bar = foo
asdf()
foo = Foo()

What happens is we first create a variable foo, of type null(Foo), which is a partially bound type, since Foo has no constructor call, yet.  Next we try to bind bar = foo, but this blocks because the new binder blocks on all reads of partially bound types.  Next we bind the call to asdf(), where the bar is accessed.  Since we have not yet created variable bar in the module scope, we create a local variable bar, and create an undefined-variable event, and block binding 'println bar' until bar is defined in the local scope.

Next, we bind a constructor call to Foo(), and since Foo is not a template class (it has no template parameters), the binder resolves null(Foo) to null(Foo()).  This unblocks the "bar = foo" assignment, and we create the module-scope variable bar.

The problem is the binder is only waiting for a loal variable named bar in function asdf() to be defined, which never happens, so the compiler fails with the message:

**********foo.rn:5: Error: Undefined identifier bar

Right now, if an identifier does not yet exist, the binder assumes it is a local variable, not global.  There is also the case where a global version of bar has not yet been created, so when assigning to bar in asdf(), it creates a local version.  The following code passes, and prints {}.

class Foo(self) {
}

func asdf() {
  bar = 1
}

foo = null(Foo)
bar = foo
asdf()
foo = Foo()
println bar

We decided not to allow identifier shadowing.  This should be a compile-time error, since "bar = 1" should assign the global version of bar to 1, which is an incompatible type vs the global version of bar.

A straightforward but slow solution would be to fix things whenever a new identifier is defined.  In the first program above, the binder could continue to create a local identifier in the undefined state, and when the global variable of the same make is declared, the compiler could check all local scopes for identifiers waiting to be defined of the same name.  Similarly, in the second program when we create a global bar variable, the compiler could look for local versions and merge them with the global one.

That is slow, and hackish.  What would a better solution look like?

Bill
Reply all
Reply to author
Forward
0 new messages