How exactly are the symbol lookup rules supposed to work in monkeyc?

[ Edit: most of this is actually buggy typechecker behavior. The runtime itself *doesn't* match what's described here. see https://forums.garmin.com/developer/connect-iq/i/bug-reports/the-type-checker-s-symbol-lookup-doesn-t-match-the-runtime ]

Given this template:

function foo() as Number {
    return 0;
}

module bar {
    module foo {
        module bar {
            const foo = 1;
            const bar = 2;
            const buz = 3;
            function baz() as Number {
                <insert code here>;
            }
        }
    }
}

I can put different things at "<Insert code here>" to test lookup rules:

  1.  "return foo();": finds the global foo, ignoring the const foo, and module foo which would be errors. Surprising, but it makes some sense (when looking up a call, keep looking until you find an actual function, ignoring any other symbols).
  2. "return buz;": finds "const buz" (as expected).
  3. "return foo;": skips over const foo (why? if there were no module foo it would have found the const foo, just like buz in 2) and finds the module foo, and then complains that it's not a Number. This feels like a bug...
  4. "return self.foo;": finds and returns the const foo (this is what I'd expect, both for this, and 3).
  5. "return bar": finds the innermost bar (so the lookup is definitely going innermost to outermost, but skipping over const/var declarations).

So what are the actual rules? It seems to be

  • when looking up a function, ignore anything thats not a function. 
  • when looking up an identifier, first look outwards to the global scope trying to find a module, but if that fails, start over, and look for a const/variable.

Is this really the way it's supposed to work? One consequence of the two phase lookup is that if you have a module (or a chunk of code in the global scope), where all internal symbol resolution works, and you wrap it in a new module, it could break (because a symbol that used to resolve correctly, now resolves to the outer module name). I can't think of any other scoped language where that would happen.

I'm not sure how clear that was, so here's what I'm trying to say:

module buz {
  const bar = 1;
  function foo() {
    return bar;
  }
}

The above works. Calling buz.foo() returns buz.bar as expected. But if I wrap it in "module bar { ... }", then calling bar.buz.foo() is an error, because bar resolves to the module instead of the constant.