Odd behavior difference between method in MyClass extends Ui.DataField file vs code in separate Utils.mc file

I have been pulling some functions out of my View.mc file into a separate Utility.mc file.  The code below works fine as a return statement of a function in the View.mc file but fails with the runtime error of cannot find symbol 'format' if it is moved to a separate Utils.mc file


return [format("$1$:$2$", [hour.format("%02d") , timeOfDay.min.format("%02d")]), ampm];

The only way I have found to resolve this error is to change the code above to:-

var string = "";
string = string.format("$1$:$2$", [hour.format("%02d") , timeOfDay.min.format("%02d")]);
return [string, ampm];

So I am guessing the problem resides with the first format.  Why is the behavior different?

The method in the View.mc file is also within the View extends Ui.DataField class.

  • I checked in the documentation and cannot find "format" as a method on either View or DataField so I'm guessing that there is an alias or import somewhere in the mix for View that you aren't seeing.

    But the pragmatist in me says just:

    import Toybox.Lang;

    And then:

    Lang.format(); 

  • Thanks will give it a go.

  • The string type object has format, so that's why string.format() works.

    Lang has it too and that will work.

    You were getting the error because the class you made doesn't have format()

  • The only method named format in the MonkeyC API is Lang.format().

    What you are seeing is weirdness in symbol lookup inside the virtual machine. I think this is the second time someone has picked up on this in the last two weeks, but prior to that I don't recall anyone else every noticing it.

    If you have this...

    using Toybox.System;
    using Toybox.WatchUi;
    
    class MyView extends WatchUi.View {
    
        function initialize() {
            var s = format("$1$", [ 999 ]);
            System.println(s);
        }
    }

    when format is called, the virtual machine will doe a runtime search for the symbol format. it searches the current class scope, then all parent class scopes. This is all consistent with most programmer's expectations. At this point, if the symbol is still not found, the enclosing module of the class is searched, and then the enclosing modules of each of the parent classes, and then the enclosing modules of those modules...

    You can verify this for yourself if you copy/paste this code into a test app and uncomment each of the implementations of the function doormat. If you build and run the app, it will print the scope that the symbol doormat was found in. If you comment out that function and repeat, you'll find the next scope that would have been searched. You can repeat until you've verified the entire search path.

    using Toybox.System;
    
    // found fourth and commented out
    //   would also have been found after XWatchUi.doormat
    //   would also have been found after XLang.doormat
    
    // function doormat(fmt, args) {
    //     return "$.doormat";
    // }
    
    module XToybox
    {
    	// found sixth and commented out
    
    	// function doormat(fmt, args) {
    	//     return "XToybox.doormat";
    	// }
    
        module XLang
        {
        	// found seventh and commented out
    
            // function doormat(fmt, args) {
            //     return "XToybox.XLang.doormat";
            // }
            
            class XObject {
            	
            	function initialize() {
            	}
            	
            	// found third and commented out
    
            	// function doormat(fmt, args) {
                //     return "XToybox.XLang.XObject.doormat";
            	// }
            }
        }
        
        module XWatchUi
        {
        	// found fifth and commented out
    
            // function doormat(fmt, args) {
            //     return "XToybox.XWatchUi.doormat";
            // }
            
            class XView extends XLang.XObject {
            
                function initialize() {
                	XObject.initialize();
                }
                
                // found second and commented out
    
    			// function doormat(fmt, args) {
    			//     return "XToybox.XWatchUi.XView.doormat";
    			// }
            }
        }
    }
    
    using XToybox.XWatchUi;
    
    class MyView extends XWatchUi.XView {
    
    	function initialize() {
    		XView.initialize();
    	}
    	
    	function test() {
    	    var s = doormat("$1$", [ 2 ]);
    	    System.println(s);
    	}
    
    	// found first and commented out
    
       // function doormat(fmt, args) {
       //     return "MyView.doormat";
       // }	
    }
    
    using XToybox.XWatchUi;
    
    class SymbolResolutionApp extends Application.AppBase {
    
        function initialize() {
            AppBase.initialize();
        }
    
        function getInitialView() {
    		var myView = new MyView();
    		myView.test();
    
            return [ new WatchUi.View() ];
        }
    
    }

    So, back to your question, the reason format isn't found when you call it without a qualifier (format vs Lang.format) from a global function is because the only scope that is searched is the global scope, and the symbol is not found there. It happens to work when you do it from within a class because all classes implicitly inherit from Lang.Object, so the runtime will search the Lang.Object scope, the global scope, and finally the Lang scope to find that symbol.

  • Thanks for the time you have taken to review and reply.  There is always a reason just sometimes it is not obvious.

    Much appreciated.