Ticket Created
over 3 years ago

CIQQA-1387

Change in runtime behavior in 4.1.4 Compiler2 Beta

Given this code:

import Toybox.Test;
import Toybox.Lang;

module LocalResolution {
    module AX {
        module BX {
            const K = 1;
        }
    }
    import LocalResolution.AX.BX;
    module CX {
        module BX {
            const K = 1001;
        }
        class X {
            const K = 2001;
        }
        function foo() as Number {
            var BX = new X();

            return BX.K; // should be X.K
        }
    }
    (:test)
    function testImport(logger as Logger) as Boolean {
        var k = CX.foo();
        if (k != 2001) {
            logger.debug("Oops - found the wrong k: " + k.toString());
            return false;
        }
        return true;
    }
}

The test passes with compilers up to 4.1.4 (non-beta). With Compiler2 Beta, it fails, because foo() returns 1.

Without the import, it works. But with the import, even though BX is a local variable, BX.K resolves to LocalResolution.AX.BX.K.

The *typechecker* in earlier versions of the compiler thought this is how things worked. But the runtime disagreed (and I've reported that as a type checker bug previously). Apparently, in this release, you've changed the runtime to match the type checker (I guess it can't really be the runtime; the compiler must be transforming BX into something more explicit, like $.LocalResolution.AX.BX. Or maybe its optimizing the constant away, even without a -O flag). The net result though, is that the runtime behavior has changed.

This seems like a serious mistake. If adding an import can cause an existing local variable to resolve to the import instead, all hell breaks loose. The import could be at global scope, in a different file, for example.

  • Maybe Garmin will be interested in your unit tests?

    Well, the project is open source, so they're all there...

    I guess my tests were mostly geared towards testing that *my* optimizer hadn't broken anything, which wasn't ideal for their purposes. I've just made some fixes, and pushed to GitHub, so going forwards, the tests should be suitable for testing that the Garmin compiler didn't change behavior.

    So, if anyone at Garmin is listening , the project is here. The tests in the sub-project tests/OptimizerTests can be run from vscode, but you need to know what to expect. Some of the tests crash at runtime, by design. Those tests have "crash" in their name. Some of the tests fail with the Compiler2Beta. They have "FailsBeta" in their name. And a few more fail with Compiler2Beta and -O2 (and failures include some of the crashing tests not crashing, because the optimizer replaced an undefined symbol with a constant value, because it looked it up incorrectly).

    From the command line, in root of the project, you can run "npm run test-unopt" which will run the tests with whichever sdk is installed, and filter out the expected failures/crashes, and report the status. It should pass with any compiler (because it *expects* some of the tests to fail with the beta). But anything marked "EXPECTED FAIL" is a change in behavior from pre-beta.

    You can also run "npm run test-garmin-opt", which will run the tests at -O2 (but only if the compiler2-beta sdk is current). That currently fails for various reasons (all of which are bugs in the optimizer).

    You can also run "npm run test-optimized", which tests that *my* optimizer didn't change the behavior relative to the pre-beta compiler.

    Finally "npm run test-remote" runs a whole slew of open source projects. It might make sense for Garmin to at least filter through these to decide which ones it wants to continue to support. All build with the old compiler (although my optimizer does some fixups to make that happen), but about 10 fail with the new. They mostly fail for sort-of valid reasons. eg some of them have invalid types, such as "catch (ex as Lang.Exception)" but without an import of Lang. This also failed on the old compiler if type checking was enabled, but was fine when it was disabled. But with the new compiler its an error whether or not the type checker is enabled.

  • Maybe Garmin will be interested in your unit tests? In a previous bug report Garmin responded an there was a sentence like "we're looking into how to improve our tests"

  • Even more fun: BX[:K] resolves to X.K (and typechecks too).

  • In fact, this starts to get insane. Inside the function foo, the plain symbol BX resolves as the local, but BX.anything resolves as the module. So if I add "const Y = 1;" to class X, and then do:

    ...
            function foo() as Number {
                var BX = new X();
                if (BX has :Y) {
                    return BX.Y;
                }
                return BX.K;
            }
    ...

    The BX in 'BX has :Y' inspects the local variable BX for a Y, and finds it. Then the return BX.Y resolves BX to the module, and crashes.

    In fact, the typechecker does complain about this one (so you have to disable it to make it run)... but I hope this makes it clear that you really can't override local variables from outside the function (and even outside the file where they're defined).