Search code examples
rakuread-eval-print-looprakudo

Compilation error only when using the repl


I am getting an error only when the code is entered line by line in the repl. It works when the whole program is pasted at once, or from the command line.

class A {
    method a () {
    return 1;
    }
}

class B {
    method b () {
    return 2;
    }
}

This is the error statement:

===SORRY!=== Error while compiling:
Package 'B' already has a method 'b' (did you mean to declare a multi method?)

This screen shot might make it clearer. On the left I just pasted the code, and on the right I entered it line by line. The code is still working but what is causing the error?

pasting vs line-by-line

For some reason, I could not reproduce this using just one class.


Solution

  • I think Comma is the bees knees. And I almost never use the repl. But I enjoy:

    • Golfing Your example is a more than adequate MRE. But I love minimizing bug examples.

    • Speculating I think I can see what's going on.

    • Searching issue queues Rakudo has two issue queues on GH: old and new.

    • Spelunking compiler code Rakudo is mostly written in Raku; maybe we can work out what this problem is in the REPL code (which is part of the compiler)?

    Golfing

    First, the bug:

    Welcome to 𝐑𝐚𝐤𝐮𝐝𝐨™ v2021.03.
    Implementing the 𝐑𝐚𝐤𝐮™ programming language v6.d.
    Built on MoarVM version 2021.03.
    
    To exit type 'exit' or '^D'
    > # 42
    Nil
    > { subset a
    * 
    ===SORRY!=== Error while compiling:
    Redeclaration of symbol 'a'.
    at line 3
    ------> <BOL>⏏<EOL>
    

    Commentary:

    1. To get on the fairway, enter any line that's not just whitespace, and press Enter.

    2. Pick the right iron; open a block with {, declare some named type, and press Enter. The REPL indicates you're on the green by displaying the * multi-line prompt.

    3. To sink the ball, just hit Enter.


    Second, golfing in aid of speculation:

    > # 42
    Nil
    > { BEGIN say 99
    99
    * }
    99
    > 
    

    (BEGIN marks code that is to be run during compilation as soon as the compiler encounters it.)

    Speculating

    Why does the initial # 42 evaluation matter? Presumably the REPL tries to maintain declarations / state (of variables and types etc) during a REPL session.

    And as part of that it's presumably remembering all previous code in a session.

    And presumably it's seeing anything but blank lines as counting as previous code.

    And the mere existence of some/any previous code somehow influences what state the REPL is maintaining and/or what it's asking the compiler to do.

    Maybe.


    Why does a type declaration matter when, say, a variable declaration doesn't?

    Presumably the REPL and/or compiler is distinguishing between these two kinds of declaration.

    Ignoring the REPL, when compiling code, a repeated my declaration only raises a warning, whereas a repeated type declaration is an error. Quite plausibly that's why?


    Why does a type declaration have this effect?

    Presumably the type successfully compiles and only after that an exception is thrown (because the code is incomplete due to the missing closing brace).

    Then the REPL asks the compiler to again try to compile the multi-line code thus far entered, with whatever additional code the user has typed (in my golf version I type nothing and just hit Enter, adding no more code).

    This repeated compile attempt includes the type declaration again, which type declaration, because the compilation state from the prior attempt to compile the multi-line code is somehow being retained, is seen by the compiler as a repeat declaration, causing it to throw an exception that causes the REPL to exit multi-line mode and report the error.


    In other words, the REPL loop is presumably something like:

    1. As each line is entered, pass it to the compiler, which compiles the code and throws an exception if anything goes wrong.

    2. If an exception is thrown:

      2.1 If in multi-line mode (with * prompt), then exit multi-line mode (go back to > prompt) and display exception message.

      2.2 Else (so not in multi-line mode), if analysis (plausibly very basic) of the exception and/or entered code suggests multi-line mode would be useful, then enter that mode (with * prompt). In multi-line mode, the entire multi-line of code so far is recompiled each time the user presses Enter.

      2.3 Else, display exception message.

    3. (Obviously there's something else going on related to initialization given the need to start with some evaluation to manifest this bug, but that may well be a completely distinct issue.)

    Searching

    I've browsed through all open Rakudo issues in its old and new queues on GH that match 'repl'. I've selected four that illustrate the range of difficulties the REPL has with maintaining the state of a session:

    One thing I haven't done is checked whether these bugs all still exist. My guess is they do. And there are many others like them. Perhaps they have a somewhat common cause? I've no idea. Perhaps we need to look at the code...

    Spelunking

    A search of the Rakudo sources for 'repl' quickly led to a REPL module. Less than 500 lines of high level Raku code! \o/ (For now, let's just pretend we can pretty much ignore digging into the code it calls...)

    From my initial browser, I'll draw attention to:

    • A sub repl:

      sub repl(*%_) {
        my $repl := REPL.new(nqp::getcomp("Raku"), %_, True);
        nqp::bindattr($repl,REPL,'$!save_ctx',nqp::ctxcaller(nqp::ctx));
        $repl.repl-loop(:no-exit);
      }
      

      Blame shows that Liz added this a couple months ago. It's very tangential to this bug, but I'm guessing methods and variables with ctx in their name are pretty central to things so this is hopefully a nice way to start pondering that.

    • method repl-eval. 30 lines or so.

    • REPL: loop { ... }. 60 lines or so.

    That'll do for tonight. I'll post this then return to it all another day.