How does Julia parse `return`?

This question comes about after I had been using the JuliaFormatter.jl package to format some existing code.

Compare the following 4 functions:

function example()
    return
end
function example()
    return
    if a == 1
        b = 2
    end
    return otherFunction(b)
end
function example()
    return a == 1 && otherFunction(4)
end
function example()
    return if a == 1
        otherFunction(4)
    else
        false
    end
end

In the first example, this is a return statement which returns nothing.

The second example is a bit harder to read. It depends on how return is parsed.

This is my guess: (I could not find anything about this in the documentation.)

  • return is a single line statement. The statement is implicitly ended by a new line.

If the above is correct, then the second example will return nothing, everytime. The if statement which follows is not part of the return statement.

The third example is tricky to read too. It returns false if a != 1, and otherwise it returns the value produced by the function call otherFunction(4).

The fourth example is exactly the same as the third. But, an if statement begins on the same line as the return statement. That makes it different from the second example, and also different from the third example.

  • Now, the return statement is not ended until the if statement is completed by the associated end. So in this case, a return statement can be multi-line.

It seems to be the same as

return (
    if a == 1 otherFunction(4) else false end
)

Is anyone able to clarify the parsing rules relating to return, and also let me know if anything I have written here is not correct?

The questions you’re raising are about parsing expressions in general, not just return! \n does terminate an expression. return returns the value to which it’s expression evaluated.

You can investigate the structure of an expression with the dump function, like dump(:(#=paste your function or any expression here=#))

When run in the REPL, this β€œdumps” what’s the internal Expr representation that Julia uses for your expression.

4 Likes

If it is complete.

Example:

x = 1 +
    2

Expression is not complete at the first newline but at the second. x is assigned 3.

x = 1
    + 2

Complete expressions at both newlines. x is assigned 1 and then the expression + 2 (unary + operation) is evaluated and not assigned to anything.

5 Likes

You can also ask the parser which output is perhaps a bit more clearn compared to dump:

julia> using JuliaSyntax

julia> str = """
       function example()
           return 1 + 1
       end
       """;

julia> tree = JuliaSyntax.parseall(JuliaSyntax.GreenNode, str)
     1:40     β”‚[toplevel]
     1:39     β”‚  [function]
     1:8      β”‚    function
     9:9      β”‚    Whitespace
    10:18     β”‚    [call]
    10:16     β”‚      Identifier         βœ”
    17:17     β”‚      (
    18:18     β”‚      )
    19:36     β”‚    [block]
    19:23     β”‚      NewlineWs
    24:35     β”‚      [return]
    24:29     β”‚        return
    30:35     β”‚        [call]
    30:30     β”‚          Whitespace
    31:31     β”‚          Integer        βœ”
    32:32     β”‚          Whitespace
    33:33     β”‚          +              βœ”
    34:34     β”‚          Whitespace
    35:35     β”‚          Integer        βœ”
    36:36     β”‚      NewlineWs
    37:39     β”‚    end
    40:40     β”‚  NewlineWs


julia> show(stdout, MIME"text/plain"(), tree, str)
     1:40     β”‚[toplevel]
     1:39     β”‚  [function]
     1:8      β”‚    function                 "function"
     9:9      β”‚    Whitespace               " "
    10:18     β”‚    [call]
    10:16     β”‚      Identifier         βœ”   "example"
    17:17     β”‚      (                      "("
    18:18     β”‚      )                      ")"
    19:36     β”‚    [block]
    19:23     β”‚      NewlineWs              "\n    "
    24:35     β”‚      [return]
    24:29     β”‚        return               "return"
    30:35     β”‚        [call]
    30:30     β”‚          Whitespace         " "
    31:31     β”‚          Integer        βœ”   "1"
    32:32     β”‚          Whitespace         " "
    33:33     β”‚          +              βœ”   "+"
    34:34     β”‚          Whitespace         " "
    35:35     β”‚          Integer        βœ”   "1"
    36:36     β”‚      NewlineWs              "\n"
    37:39     β”‚    end                      "end"
    40:40     β”‚  NewlineWs                  "\n"

6 Likes

In general, how would one know if an expression is complete or not complete.

There seem to be obvious or intuitive examples, for example the example given of

x = 1 + 
      2

it seems obvious this is not complete, because + must be the binary + operator here, and there is nothing on the RHS.

However in some cases, such as the cases from my OP, where is less obvious.

Is the way to do it using JuliaSyntax.parseall? Or are there other methods/rules which can be used to figure this out?

FWIW, all cases in your OP are obvious to me but that probably depends on how much programming (or julia) experience you have.

Adding a linter error for unreachable code (e.g. any code after return) would be a nice addition if anyone is up for contributing to the language server.

2 Likes

I think the only odd one out is the implicit return of nothing. Without that special case, return behaves just like a variable declaration and you could always replace

return $expr

with

retval = $expr
return retval

The examples in the original post all make perfect sense once you internalize that in Julia, every expression (including control flow) returns β€œsomething” (which might be nothing) and expressions like

x = if a<0
    :smaller
else
    :bigger
end

or

x = a == 1 && otherFunction(4)

are quite idiomatic.

1 Like