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]

  • Here's a head scratcher: When I use the optimizer for my FR235 the code works as expected. When I compile without it, I get an unexpected type error in onLayout when the simulator starts. Apparently the Garmin compiler can't handle null Symbols, whereas whatever the optimizer is doing makes it work.

  • But case in point:https://forums.garmin.com/developer/connect-iq/f/discussion/294852/big-update-to-prettier-extension-monkeyc/1688905#1688905

    What's passed to the Garmin Compiler isn't the original source, so something like this crash may be obscured.  Seems it would also throw off line numbers, etc, in the ciq_log file and ERA, as the Garmin compiler  doesn't have the original source.

  • Maybe re-read the post? The code compiles with and without the optimizer. The un-optimized code crashes in the simulator. I don't know if it works on the real watch, why would I try if it doesn't work in the simulator? The optimized code works. Yes, I had explicit casts, but that wasn't good enough. I changed the nulls to a symbol I don't use ":none" to get the logic to not die on a "unexpected type".

  • It may compile in both cases, but this optimizer obscures a run time error, which to me is a problem.  How do you resolve an ERA that's not compiled using the original code?  While the optimizer may save you a few bytes at runtime, it makes resolving runtime issues harder.

  • Yes, I had explicit casts, but that wasn't good enough. I changed the nulls to a symbol I don't use ":none" to get the logic to not die on a "unexpected type".

    It seems that in your case, it’s the type cast that’s obscuring the run-time error, by preventing the type checker from noticing a problem. I assume that if you remove the type cast, the code no longer compiles, right? This optimizer may apply additional logic to “fix” your code due to the presence of the cast, but I wouldn’t expect that to happen in the absence of the optimizer.

    Monkey Types is a compile-time type checking system on top of Monkey C, similar to Typescript / Javascript. No trace of Monkey Types remains at run-time.

    A type cast simply tells the type checker to ignore the type it’s determined for an expression and use the type you provide instead. It does *not* change the run-time type, which is a different thing. (It’s not like a cast in Java, C#, or C++.)

    That’s why a type cast can *never* solve a run-time type error (assuming you’re not using this optimizer). At worst, it can actually allow a run-time type error to happen (by preventing the type checker from catching a legitimate bug.) At best it allows correct code to compile which wouldn’t otherwise compile, due to a limitation in the compile-time type system.

    That’s why I’ve argued that Monkey Types should make it harder to cast between two incompatible types, and that it shouldn’t use the same syntax (X as Y) for casts and type declarations. A cast is a type system escape hatch that should rarely be used, whereas a type declaration is a fundamental part of the system.

  • To be fair to the optimizer, by looking at the code it produces it has been helpful in giving hints about how I can make my own changes that will work with the compiler as-is and yet get similar benefits. I have been able to reduce the code and data size by more than 20% just with my own edits. Peak memory is less than 48k which will allow it to run on almost every watch now, something I couldn't do a few months ago.

  • Monkey Types is a compile-time type checking system on top of Monkey C, similar to Typescript / Javascript. No trace of Monkey Types remains at run-time.

    I'm not so sure about this. If I have a function like "myFunc(arg1 as Number, arg2 as Boolean)" I'll get an unexpected type runtime error if I pass a string like "my string" in arg1, the same behavior as with the API functions.

  • As I've said before, the best optimizer is located between the chair and keyboard and there is a learning curve for this with CIQ.  Learn from what this extension does to your original code, and you'll find you don't need it anymore as it can make using ERA harder.

  • Monkey types is strictly a compile time thing.  No changes at runtime.  Sounds like you got a runtime error which this nor basic typechecking catches.

  • If I have a function like "myFunc(arg1 as Number, arg2 as Boolean)" I'll get an unexpected type runtime error if I pass a string like "my string" in arg1, the same behavior as with the API functions.

    I could be wrong, but without seeing the full code snippet and the line on which the runtime error occurs, I'd assume that the runtime error is happening when arg1 is *used* (e.g. in an expression like arg1 + 42), not when myFunc is called.

    If you read Garmin's own docs on Monkey Types, they make it clear that:

    - Monkey Types is compile-time only

    - Run-time type checking is limited to instanceof and has (which existed before Monkey Types, and work in a completely different way)

    One of the goals of Monkey Types was to not add any runtime overhead. This allows Monkey Types to work on all Connect IQ compatible products out of the gate, but it does add a cost when it comes to runtime type checking. Put simply: while at compile time you have access to an expressive type system, at runtime instanceof and has have the same limits as they had before.

    Seems it would also throw off line numbers, etc, in the ciq_log file and ERA, as the Garmin compiler  doesn't have the original source.

    This is a good point. If I were to use this optimizer, I would either:

    - source-control the generated code

    - source-control a reference to the specific extension version / ensure there's a way to do reproducible builds

    i.e. If old version X of the optimizer produces different code than current version Y, that becomes a problem if I can't check out old code which originally used version X and get the same results as I did before.

    I don't know exactly how the extension works since I don't use it, but in my ideal world:

    - the extension would be de-coupled from the actual prettier/optimizer (much like the Monkey C extension is decoupled from the SDK version)

    - the prettier/optimizer would be locally installed using npm and its version specified in package.json. That way, the current version of the optimizer could be frozen via package-lock.json to facilitate reproducible builds