Under Review
over 1 year ago

Referencing an imported module doesn't run its parent's

If I have a program like:

import Toybox.Application;
import Toybox.System;

class MyApp extends Application.AppBase {

    function initialize() {
        AppBase.initialize();
        System.println(Rez.Strings.AppName);
    }
    ...
}

it prints out a random (well not really) number. It should be a Symbol, but I already filed a bug against that.

But if I import Rez.Strings, it doesn't work:

import Toybox.Application;
import Toybox.System;
import Rez.Strings;

class MyApp extends Application.AppBase {

    function initialize() {
        AppBase.initialize();
        System.println(Strings.AppName);
    }
    ...
}

Now it prints null.

But if I change it to

import Toybox.Application;
import Toybox.System;
import Rez.Strings;

class MyApp extends Application.AppBase {

    function initialize() {
        AppBase.initialize();
        System.println(Strings.AppName);
        var x = Rez;
        System.println(Strings.AppName);
    }
    ...
}

I get null from the first println, and a random number from the second. So reading Rez fixes it.

I think the problem is that the <init> routine for a module runs the first time you mention that module. So if I say Rez.Strings, the init for Rez runs. Similarly, just assigning Rez to a local seems to cause it to run. But going directly for Strings via an import bypasses that, and we get to read uninitialized data (or rather, initialized to null).

My guess is that when getm or getv fetch a module, the runtime checks to see if the module's init has already run, and if not runs it. The bug seems to be that it should first check that any outer module's init has also run.

I've verified the bug with 4.1.5, 4.1.7 and 4.2.0beta2

Parents Comment Children
  • That's why I asked about the background permission and scoping

    Right - because not having the background permission would definitely explain why it works when you use Rez.Strings.MyApp, fails when you say Strings.MyApp *before* mentioning Rez, and works when you say Strings.MyApp after mentioning Rez...

  • Srings.AppName is clearly referencing that resource.

    And initialize() in AppBase is run when both the foreground and background service run.  That's why I asked about the background permission and scoping.

  • Does your app have a background service (or just the background permission

    Have you even read the bug report? This has nothing to do with resources, and nothing to do with background permissions. Its about when a module's (compiler generated) <init> method gets called (and yes I'm talking about Garmin's compiler, not my extension).

    I'll try one more time to explain it to you.

    If you "import Rez.Strings", and then any access to Strings.foo before you directly "mention" Rez will read null. That shouldn't be possible. So after the import, if the first thing you do is this (and it doesn't matter *which* string you access, as long as its *a* string):

      var x = Strings.MyApp;
      var y = Rez.Strings.MyApp;
      var z = Strings.MyApp;
      

    Then x will be null (because the Rez module's init hasn't run yet), y will trigger the init, and get the correct "random" number, and z will be equal to y. All later uses of Strings.<whatever> will work now that Rez's init method has run.

    And this happens for *any* module with an init, not just Rez.

    I used a resource as a simple illustration of the problem to indicate how pervasive it is (ie *everyone* could be affected by it, because it affects Rez.Strings). Whereas nested user modules where the outer module requires an init method are probably rare.

    And the *reason* you'd want to "import Rez.Strings" and then say "Strings.Foo" everywhere instead of "Rez.Strings.Foo" is because each access to Strings.Foo is 14 bytes smaller than each access to Rez.Strings.Foo.