Feature #18040
closedWhy should `foo(1 if true)` be an error?
Added by bughit (bug hit) over 3 years ago. Updated over 3 years ago.
Description
There's no ambiguity here that should require another set of parens foo((1 if true))
Updated by jeremyevans0 (Jeremy Evans) over 3 years ago
- Tracker changed from Bug to Feature
- Backport deleted (
2.6: UNKNOWN, 2.7: UNKNOWN, 3.0: UNKNOWN)
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) over 3 years 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) over 3 years 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) over 3 years 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) over 3 years 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) over 3 years 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#L1481You 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) over 3 years 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:
-
- There is implicit casting from a statement to an expression within a method definition body, or
-
- 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) over 3 years 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) over 3 years 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.