Project

General

Profile

Actions

Feature #18040

closed

Why should `foo(1 if true)` be an error?

Added by bughit (bug hit) 4 months ago. Updated 4 months ago.

Status:
Rejected
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-core:104643]

Description

There's no ambiguity here that should require another set of parens foo((1 if true))

Updated by jeremyevans0 (Jeremy Evans) 4 months ago

  • Backport deleted (2.6: UNKNOWN, 2.7: UNKNOWN, 3.0: UNKNOWN)
  • Tracker changed from Bug to Feature

1 if true is a statement and not an expression, and you can only pass expressions as method arguments. (1 if true) is an expression, which is why it is allowed. Consider how foo(1 if true) would work with multiple arguments, and you can probably figure out what the problems are. Note that foo (1 if true) (with a space) works, since that's a method call without parentheses, with a single argument of (1 if true).

This isn't a bug, it's expected behavior. I'm not sure whether changing the grammar to support this type of one argument method call is possible, but I'm guessing nobu (Nobuyoshi Nakada) would know best. In any case, adding support for this would be a feature request.

Updated by bughit (bug hit) 4 months ago

jeremyevans0 (Jeremy Evans) wrote in #note-1:

1 if true is a statement and not an expression

1 if true is an expression (as pretty much everything in ruby) whose value is nil, if the condition is false, else the modified subexpression. Parens don't turn it into an expression, it already is one.

Updated by bughit (bug hit) 4 months ago

def foo
  2
  1 if false
end

p foo

foo returns nil, the value of the last expression 1 if false, no parens required since there's no ambiguity, as there isn't any in foo(1 if true)

Updated by jeremyevans0 (Jeremy Evans) 4 months ago

bughit (bug hit) wrote in #note-2:

jeremyevans0 (Jeremy Evans) wrote in #note-1:

1 if true is a statement and not an expression

1 if true is an expression (as pretty much everything in ruby) whose value is nil, if the condition is false, else the modified subexpression. Parens don't turn it into an expression, it already is one.

You may want to read the definition of stmt in parse.y, particularly this part: https://github.com/ruby/ruby/blob/eed5e8f796ab18e2e0a436dab83e35504ae3ded0/parse.y#L1481

Updated by bughit (bug hit) 4 months ago

jeremyevans0 (Jeremy Evans) wrote in #note-4:

You may want to read the definition of stmt in parse.y, particularly this part: https://github.com/ruby/ruby/blob/eed5e8f796ab18e2e0a436dab83e35504ae3ded0/parse.y#L1481

You can call it a statement all you want but if it produces a value, its an expression. And it does produce a value.

Here's the description of method return from "the ruby programming language" book:

If a method terminates normally, then
the value of the method invocation expression is the value of the last expression evaluated
within the method body.

the last expression in my example is 1 if false with no parens

Updated by jeremyevans0 (Jeremy Evans) 4 months ago

bughit (bug hit) wrote in #note-5:

jeremyevans0 (Jeremy Evans) wrote in #note-4:

You may want to read the definition of stmt in parse.y, particularly this part: https://github.com/ruby/ruby/blob/eed5e8f796ab18e2e0a436dab83e35504ae3ded0/parse.y#L1481

You can call it a statement all you want but if it produces a value, its an expression. And it does produce a value.

The reason that foo(1 if false) is a syntax error is due to a parsing failure. The parser considers it a statement (stmt) and not an expression. What the parser considers statements can still return values in Ruby.

In any case, the reason for the issue remains the same. The parser doesn't allow a statement (stmt) to be used directly as a method argument. You have to wrap it in parentheses to use it as a method argument. Trying to support a stmt used directly as a method argument (arg), results in 132 shift/reduce conflicts and 2056 reduce/reduce conflicts in the parser. I doubt there is any way to support it as a general method argument. As I mentioned it may be possible to get it to work in only the single argument case by special casing it. However, I couldn't get that to work without shift/reduce conflicts. Maybe nobu (Nobuyoshi Nakada) would have more luck. Even then, we would have to decide if we want to support that, because it would result in inconsistent syntax, since foo(1 if false) would be allowed, but foo(1 if false, 2) (the multiple argument case) would not.

Updated by sawa (Tsuyoshi Sawada) 4 months ago

It looks like the reason bughit is insisting is because of the description in the book that they cite. And indeed, if 1 if false in the method definition given in note 3 is not an expression, then 2 is, and it could be taken that the citation implies that 2 (= "the last expression evaluated within the method body") would be the return value of the method invocation, contrary to fact. So it looks like the logical possibilities are:

  • 1. There is implicit casting from a statement to an expression within a method definition body, or
  • 2. The book is not accurate (which is an issue because one of its authors is Matz, meaning that it is official).

Updated by bughit (bug hit) 4 months ago

implicit casting from a statement to an expression within a method definition body

It's not just the method body, x if y without parens is an expression in any compound expression block which evaluates to its last expression value. That's a lot of contexts (loops, conditional, blocks, methods, lambdas, class bodies, module bodies, begin end, (x; y)) probably forgetting some. So what makes more sense, that it's conceptually an expression or a "statement" which oxymoronically produces a value?

Updated by Eregon (Benoit Daloze) 4 months ago

  • Status changed from Open to Rejected

One way to see it is to just forget that Jeremy called it statement.
One of the parser rule is named stmt and yes the produced AST node does return a value, like everything in Ruby.

It's not realistic for the Ruby grammar to accommodate what you ask in general as Jeremy said,
so I think it's safe to close this as "not realistically fixable". You are free to try to fix it though.

Actions

Also available in: Atom PDF