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]

  • v2.0.100 is out.

    This version fixes a bug with goto defs for "types" as describe here, and fixes goto defs for modules so that it shows every declaration of the module.

    It also includes a big rewrite of the analysis pass to properly analyze const, variable and enum initializers. Since this involved a significant restructuring of the code, I've only applied it to the analysis pass for now; builds will work as before (apart from some minor improvements to the optimizer). If the analysis pass seems to be doing ok, I'll update the optimization pass to use it in the next release.

  • I see two new types of warnings:

    import Toybox.Lang;
    
    class Test {
        enum Enum {
            ENUM_VALUE = 1 << 0,
        }
    
        // The type :value cannot be converted to Number because they have nothing in common
        function test(value as Enum or Number) as { :value as Number } {
            // Unexpected types for operator '&': [Null or Float or Double or Char or String vs Number]
            return { :value => (value & ENUM_VALUE) == ENUM_VALUE ? 1 : 0 };
        }
    }

  • Thanks!

    v2.0.101 should fix both of those. Please let me know if you find anything else.

  • Everything is fixed. Thank you!

    There is one issue, that bothers me a bit since "analyze the whole workspace" functionality was introduced. If I have some warning for code in barrel, I see it not only in barrel itself, but also in "bin/optimized/raw-barrels" for every project, that uses this barrel. I'd like not to have such duplicates, as my Problems panel is flooded by these.

    Honestly, I have no idea, how and where the fix for this could've been implemented. I'd like to see barrel's warnings only when developing a barrel, and not when using it, so from this point of view "raw-barrels" should be somehow excluded from analysis (or typecheck should be disabled for it or something like that), but also I don't want to lose the ability of going to definitions from project to barrel and finding references from barrel in project, so it looks like the "raw-barrels" analysis should still be performed.

    I'd be happy to get away from this by doing some VS Code configuration, or by manipulating "sourcePath" in jungle files, or by adding some patterns to "prettierMonkeyC.ignoredSourcePaths", all of which I tried with no luck.

    Is there something that can be done, except for eliminating all errors and warnings for the entire workspace, that is not possible due to the nature of my projects?

  • Is there something that can be done

    Excellent points. And reading this made me realize there's another issue. I shouldn't (but currently do) let you rename a symbol if any of its uses/definitions occur inside a compiled barrel (because its just going to rename the symbol inside the generated raw-barrels file).

    So I need to add some special case code for raw-barrels anyway.

    I see a few possibilities for controlling warnings:

    • An option to turn off warnings in barrels (whether compiled or not). People (like you) who use a multi-root workspace with both the app and the barrel open would want to enable this option; people who just have the app open would probably want to see the barrel warnings.
    • An option to only show warnings in open files. This is what typescript does (with no option to show everything), and I personally find it very annoying, because it can look like everything is fine until you build.
    • A regex, or set of regexes that could be used to ignore diagnostics. There could even be path-based and content-based regexes, so that you could ignore all warnings from particular directories, or all warnings matching a particular pattern.

    And since these would all be options, I guess there's no reason not to do all three...

  • rename a symbol

    Speaking about renames, I've just discovered, that it is possible to rename function declared in module. Earlier I've tried to rename class variable or function with "Unable to rename ..." message, and thought, that renames are implemented only within function scope, as it is relatively easy to find all usages and do renaming correctly. I thought, that other renames are just not implemented yet, as it is much harder to track everything, including such tricky things as "method(:callbackFunction)" and "clazz[:variable]". Probably, it might not be that difficult to allow class members renaming just as PMC does this for modules now.

    An option to only show warnings in open files.

    This feature already exists as a core VS Code functionality: https://code.visualstudio.com/docs/getstarted/tips-and-tricks#_errors-and-warnings. In this gif "filter" icon is clicked, and there is "Show Active File Only" option. It filters out other problems, but in the explorer tree files remain red colored, which is kind of annoying too. And I don't use this option because of the reason you've described.

    A regex, or set of regexes that could be used to ignore diagnostics.

    I think, that would be the best possible option for my case.

  • it is possible to rename function declared in module

    The difference between modules and classes is that in general, you call a module function on the module itself - ie MyMod.func(), while with class methods you call it on an object: x.func().

    When I first wrote the extension, I didn't do any type analysis, so in general I had no idea of the type of x in x.func(), so I was cautious.

    Now that the optimizer does type analysis, I could be more aggressive; but that does depend on a project being fully typed. Or at least I would need to check that every expression that involves <something>.func is known to resolve to the function we're renaming, or known to not resolve to the function we're renaming. As I write this, I'm realizing that visitReferences, which is the engine behind goto-refs and rename (and lots of other front end analysis) should be able to tell me that for each occurrence of func, so maybe this is relatively easy.

    And yes, in general, if you mention :func anywhere in the project, rename will refuse to rename func. But there are exceptions - in particular the analyzer recognizes method(:func) as a reference to func.

  • It seems, there is a new issue due to "formatAstLongLines" fix.

    At some moment of doing regular code editing stuff inside barrel the number of errors raises rapidly to 150+: there are lots of "Undefined symbol ...", "... expects ... arguments, but got ...", "Unable to resolve type ...". And the following message in the VS Code console appears:

    [error] TypeError: Cannot read properties of undefined (reading 'filter')
    	at formatAstLongLines (.../extensions/markw65.prettier-extension-monkeyc-2.0.101/node_modules/@markw65/monkeyc-optimizer/build/chunk-O23K24OV.cjs:19852:39)
    	at .../extensions/markw65.prettier-extension-monkeyc-2.0.101/build/src/extension.js:20:15624
    	at as (.../extensions/markw65.prettier-extension-monkeyc-2.0.101/build/src/extension.js:20:15857)
    	at .../extensions/markw65.prettier-extension-monkeyc-2.0.101/build/src/extension.js:20:13849
    	at Array.map (<anonymous>)
    	at .../extensions/markw65.prettier-extension-monkeyc-2.0.101/build/src/extension.js:20:13590

    Command line analysis for barrel itself and for project with this barrel finish without these errors. And there are no unfinished expressions (like missing semicolon, etc., that make source file unparseable). After VS Code restart everything comes back to normal.

    UPD: I don't know, whether this will help or not, but I've surrounded "formatAstLongLines" body with "try { ... } catch (ex) { console.log(JSON.stringify(node)); throw ex; }", and the first logged object was the following:

    {
        "type": "ClassDeclaration",
        "id": {
            "type": "Identifier",
            "name": "View",
            "start": 546103,
            "end": 546107,
            "loc": {
                "source": "api.mir",
                "start": {
                    "offset": 546103,
                    "line": 6550,
                    "column": 15
                },
                "end": {
                    "offset": 546107,
                    "line": 6550,
                    "column": 19
                }
            }
        },
        "superClass": null,
        "body": {},
        "attrs": {
            "type": "AttributeList",
            "system": {
                "file": "api/WatchUi.mb",
                "line": 1597,
                "disableBackground": true,
                "minSdk": "1.0.0"
            },
            "access": null,
            "start": 546002,
            "end": 546088,
            "loc": {
                "source": "api.mir",
                "start": {
                    "offset": 546002,
                    "line": 6549,
                    "column": 9
                },
                "end": {
                    "offset": 546088,
                    "line": 6549,
                    "column": 95
                }
            }
        },
        "start": 546002,
        "end": 550085,
        "loc": {
            "source": "api.mir",
            "start": {
                "offset": 546002,
                "line": 6549,
                "column": 9
            },
            "end": {
                "offset": 550085,
                "line": 6590,
                "column": 10
            }
        }
    }

    There are a lot more lines got logged, but I guess, the first one is the most important. All others have empty "body" too as I see, all are "ClassDeclaration", except for one "ModuleDeclaration", that is my barrel module. I was editing custom view class, while breakpoint on "throw ex" was hit. I'm using 7.2.0, and its "api.mir" at line 6550 and around it declares enums I don't use, if it matters.

  • Should be fixed by v2.0.102

    This was weird. I modified formatAstLongLines so that when applied to class and module nodes it would strip out any contained modules and classes. That's because it's also used for debugging, and contained modules and classes are now processed separately.

    The changes I made caused it to throw an exception if the body field was null - the parser never generates null bodies, so the types are declared as non nullable, so it seemed like it shouldn't be a problem.

    But I had forgotten that in the extension, it uses formatAstLongLines to format modules and classes for things like hover information; but when it does so it null's out the body field so you just get "module foo;", rather than "module foo { /* all of foo's contents */}". So that caused the exception.

    But worse, the way the extension did it was to copy the field, set it to null, call formatAstLongLines, and then restore the field. Since formatAstLongLines threw an exception, the field was never restored. This meant that gradually, the bodies of anything you hovered over, or tried to complete etc would go missing, causing all the undefined symbol issues.

    Anyway, I fixed formatAstLongLines, and I also changed the extension so that it clones the nodes, and sets the clone's body field to null. That way it doesn't have to be restored (and tbh, save and restore wasn't really safe anyway because formatAstLongLines is asynchronous - another thread might have looked at the field before it was restored).

  • Theoretically, could an aggressive optimizer improve performance by aliasing all qualified names with a `$.generatedA.generatedAC` where it generates an N-level deep set of modules which store references to everything the program uses?

    Thinking out loud: You want to minimize lookup times, and each lookup is a linear top-to-bottom scan of that namespace.  If a namespace gets too big, then scans slow down.  If you alias everything to a single global scope `$.generatedIdentifierA`, `$.generatedIdentifierB`, etc. then the linear scan of that namespace is slow.

    But if you structure it like a tree, N levels deep of namespaces, then lookups could theoretically be faster.  You'd be trading off a bit of code size though.

    Then again, I don't know exactly how MonkeyC's top-level scope / namespace is structured.  Are using/import statements stored as the highest items in the scope, so linear scans always hit them first?

    I dunno, thinking out loud, curious what others think.