What does the parser think this invalid construction means?

I discovered today that this line:

if <cond expr>: return true; else return false; end

always returns true, whatever the value of x.
The behaviour is the same in the REPL and in functions.

I admit I thought that the parser would pick up syntax errors,
so I wasted time focussing on the logic of the expression, instead
of checking the punctuation :frowning:

However the parser apparently thinks it’s valid, because there is no
error message. What does the parser think it means?
It has a colon but it’s not an iterator.

I may be wrong, but my reading of

julia> Meta.@lower (if false: return true; else return false; end)
:($(Expr(:thunk, CodeInfo(
1 ─ %1 = Main.:(:)
└──      return true
2 ─ %3 =   dynamic (%1)(false, nothing)
└──      goto #4 if not %3
3 ─      return nothing
    @ none within `unknown scope`
   ┌ @ REPL[5]:1 within `macro expansion`
4 ─│      return false
   └
))))

is that return true is executed immediately after the evaluation of the colon, because that’s still part of the condition (nothing stopped the condition expression). For example

julia> if return 42; end
42

is also valid because the “condition” of the if contains return and that returns immediately no matter what, the if doesn’t even have a chance to do anything.

1 Like

The key points are that:

  1. return X is a valid expression in any context that would allow arbitrary Julia values, and simply returns immediately once it is executed (so that it need not have a “value”). This is what allows idioms like condition && return foo to work.
  2. The colon operator : has higher precedence than if for the parser, and return has higher precedence than :, so if you do if foo:bar; else ...; end, the foo:bar is parsed as a call to the Colon() operator even if foo and/or bar are a return statement
1 Like

OK, thanks.
I never enjoyed learning precedence rules. I’m just going to put parentheses everywhere they’re relevant. As I understand it so far, there’s no penalty for doing that.

1 Like

FWIW I don’t think return should be allowed here. I consider this a bug

Bugs aren’t documented:

  When used in a top-level expression (i.e. outside any function), return causes the entire current top-level
  expression to terminate early.

The lowered code suggests it returns before the colon could be called, and indeed it does:

julia> struct LoudFalse; LoudFalse() = (println("False!"); new()) end

julia> Base.:(:)(::LoudFalse, y::Bool) = (println("colon!"); false:y)

julia> if LoudFalse(): return true; else return false; end
False!
true

If we don’t return early, the colon call would error:

julia> if LoudFalse(): true; else return false; end
False!
colon!
ERROR: TypeError: non-boolean (UnitRange{Bool}) used in boolean context

I didn’t say the range was constructed, the colon is referenced.

this docstring seems super unrelated. that’s about top-level expressions, not if conditional blocks?

I figured, but “evaluation of the colon” made it a bit ambiguous whether it meant referencing the callable : or calling it. People usually mean evaluating a function to be a call. In a specific Julia context,eval-uating Expr-essions is a whole different deal.

We’re talking about an if block at top-level. Granted, that’s a trivial statement because all code is nested inside a top-level expression.

I understand that’s the current example being used, but it has the same behavior inside functions. I read that docstring as describing the behavior of return when used in statement position at top level, not as declaring that return can be inserted anywhere into any expression.

The docstring said “in a top-level expression” and “entire current top-level expression” exactly to distinguish it from the return subexpression. You’d be right if it said “as a top-level expression”, though it’d be unusual to let a top-level return statement exist to basically do nothing if it were otherwise disallowed as a subexpression except for function expressions.

I’d only use it in blocks that mimick one-off function calls, it can get downright bizarre:

julia> struct Y
        return 0
        Y() = println("hello") # removes the default definition, but isn't evaluated
       end
0

julia> Y()
ERROR: MethodError: no method matching Y()
3 Likes