Big update to prettier-extension-monkeyc

I've posted about prettier-extension-monkeyc before, but I've added a bunch of new features that developers will probably like (well, I've been missing them, so maybe you have too).

The new features it implements for VSCode include:

  • Goto Definition. Point at a symbol, Ctrl/Cmd click, and it will take you to the definition. Or F12
  • Goto References. Right click on a symbol and select "Goto References". It will show you all the references. Or Shift-F12
  • Peek Definition/Peek References. Same as above, but in a popup window so you don't lose your place in the original document.
  • Rename Symbol. Right click on a local, function, class or module name, and select "Rename Symbol". It will rename all the references. It doesn't yet work for class members/methods.
  • Goto Symbol. Type Ctrl/Cmd-Shift-O and pick a symbol from the drop down (which has a hierarchical view of all symbols in the current file). This also appears as an outline across the top of the file.
  • Open Symbol By Name. Type Ctrl/Cmd-T, then start typing letters from a symbol name. A drop down will be populated with all matching symbols from anywhere in your project.

Older features include a prettier based formatter for monkeyc, and a monkeyc optimizer that will build/run/export an optimized version of your project.

[edit: My last couple of replies seem to have just disappeared, and the whole conversation seems to be in a jumbled order, so tldr: there's a new test-release at https://github.com/markw65/prettier-extension-monkeyc/releases/tag/v2.0.9 which seems to work for me on linux. I'll do more verification tomorrow, and push a proper update to the vscode store once I'm sure everything is working]

  • Converting my code to SDK 9.1.0 compatible I get to this annoyance.

    import Toybox.Application;
    import Toybox.Application.Properties;
    import Toybox.Application.Storage;
    import Toybox.Lang;
    import Toybox.System;
    
    (:inline)
    function getConfig(key as String) as Properties.ValueType or Null {
        var val;
        if (Application has :Properties) {
            val = Properties.getValue(key);
        } else {
            val = ((POCApp as Object?) as POCApp).getProperty(key) as Properties.ValueType?;
        }
        return val;
    }
    
    function getConfigNumber(key as String, defaultValue as Number) as Number {
        return toConfigNumber(getConfig(key), defaultValue);
    }
    
    function getStorage(key as String) as Storage.ValueType or Null {
        var val = null;
        if (Application has :Storage) { // ciq_2_4_0
            val = Storage.getValue(key);
        }
        return val;
    }
    
    function getStorageNumber(key as String, defaultValue as Number) as Number {
        return toConfigNumber(getStorage(key), defaultValue);
    }
    
    function toConfigNumber(value as Properties.ValueType /*or Storage.ValueType*/ or Null, defaultValue as Number) as Number {
        if (value instanceof Lang.Boolean) {
            return value ? 1 : 0;
        }
        // this should cover Float, Double, Number, String, Long, Char, Symbol
        value = value != null && value has :toNumber ? value.toNumber() : null;
        return value != null ? value as Number : defaultValue;
    }

    If I add "or Storage.ValueType" to the 1st parameter of toConfigNumber, then I get this error:

    ERROR> fr245: Config.mc:19:27: Argument 1 to $.toConfigNumber: passing Null or Boolean or Number or Long or Float or Double or String or Array<Toybox.Application.Properties.ValueType> to parameter Null or Boolean or Number or Long or Float or Double or Char or String or Array<Toybox.Application.Properties.ValueType> or Array<Toybox.Application.Storage.ValueType> or Dictionary<Boolean or Number or Long or Float or Double or Char or String, Toybox.Application.Storage.ValueType> or Toybox.Application.WatchFaceConfig.Id or Toybox.BluetoothLowEnergy.ScanResult or Toybox.Complications.Id or Toybox.Graphics.BitmapReference or Toybox.Lang.ByteArray or Toybox.WatchUi.AnimationResource or Toybox.WatchUi.BitmapResource is not type safe [more info: github.com/.../Extra-Reference-Type-Checks-(prettierMonkeyC.extraReferenceTypeChecks)]

    Until now it worked with PropertyValueType. I know it's just a warning, but maybe there can be done something about it? I mean I even guess that it's a problem that it's hard to detetmine what value.someMethod() can do, and so value.toNumber() is kind of risky, but I get the same error if I remove this line from toConfigNumber:

    value = value != null && value has :toNumber ? value.toNumber() : null;
    return value != null ? value as Number : defaultValue;
    But can't you determine that value is just passed along?
  • You're talking about Garmin's optimizer here, yes?

  • No. When I compile without Prettier Optimizer, then there's no warning at all. Only when I compile with Prettier I get this error(warning)

  • It's complaining about passing the result of getConfig(key) (not sure what that is) to a parameter of type Storage.ValueType.

    So yes, in theory it could analyze toConfigNumber and see that it doesn't do anything dangerous with value, and suppress the warning - but that's not something I've implemented (it does do some analysis for very simple callees, but that doesn't apply here).

    As far as I can see, the warning has nothing to do with the toNumber call - I'm not quite sure what you're asking?

  • Hmm. As it says in the header, I was replying to  https://forums.garmin.com/developer/connect-iq/f/discussion/294852/big-update-to-prettier-extension-monkeyc/2018297 which doesn't mention an error or warning. So again, I assume your post about the failure to optimize constants is talking about Garmin's compiler, not my optimizer...

  • I edited my previous comments to address the missing information in your last 2 comments.

  • Hi,

    Finally time to try Prettier - as a good exercise and to try and stave off the datafield stack limit I hit before I have to do a bigger refactor.

    However, not all went well...

    Starting optimization step...
    ERROR: Internal: TypeError: Got exception `Cannot read properties of null (reading 'argument')' while processing node $.Barrel_Communications.LicenseHandler.initialize:FunctionDeclaration from c:/Projects/Garmin/Barrels/Barrel_Communications/LicenseHandler.mc:23:38

    Happy to share this segment below... Is this because I have a Method as parameter? Should this work? Would moving the Method to a setter help? 

    module Barrel_Communications
    {
        class LicenseHandler
        {
            ...;

            hidden var mcallbackResult as Method(szLicenseType as String, szLicenseExpiryDateTime as String, szLicenseException as String) or Null;

            function initialize( callbackResult as Method(szLicenseType as String, szLicenseExpiryDateTime as String, szLicenseException as String) as Void,
                                    ... other params (6 + method) e.g. ,
                                    szLicenseId as String,
                                    ...
            {
  • It is definitely supposed to work.

    Adding your var mcallbackResult to one of my classes, and setting it to null in the initializer is enough to repro the bug - so I should be able to figure it out.

    Thanks for the report.

  • The problem is caused by not giving the method a return type.

    It's definitely my bug, and I'll fix it, but you can workaround the bug by specifying the return type of the Method. Assuming the Method returns the type CallBackResult, you would write:

            hidden var mcallbackResult as (Method(szLicenseType as String, szLicenseExpiryDateTime as String, szLicenseException as String) as CallBackResult) or Null;

    Edit: Sorry, that's pretty hard to read. Here it is as formatted by PMC:

        hidden var mcallbackResult as
            (Method
                (
                    szLicenseType as String,
                    szLicenseExpiryDateTime as String,
                    szLicenseException as String
                ) as CallBackResult
            )?;
    

  • That makes sense and gets me a compiled build - excellent, thank you.

    My next issue appears to be that my customComparator seems to have been collapsed / eaten in optimisation and hence resulting in a runtime "Symbol Not Found" -- this class below is collapsed to an empty class in the optimised source so calling:

    dictRet.sort(new customComparator() as Comparator);

    fails. Any suggestions?  My dictionary structure is a simple set of strings and numeric areas (hence sorting by area)

        class customComparator
        {
            function compare(a as tilesOptimised, b as tilesOptimised) as Number
            {
                // return 1 if a is bigger than b
                // if smaller return -1
                //  - but we want the LARGEST first so swap this ordering around

                if (a.areaTile > b.areaTile)
                {
                    return -1;
                }
                else if (a.areaTile < b.areaTile)
                {
                    return 1;
                }
                else
                {
                    return 0;
                }
            }
        }