Bug #20931
closedUsing `in` as an expression requires extra parentheses
Description
TBH - I'm not sure if this is a bug or not - but it certainly surprising behavior and I'd at least like to understand it.
Given a hash t - that can be pattern matched: t = {a: 1, b:1 }
r = t in {a: 1, c:1 } # returns `false`
r # {a: 1, c: 1} wat
Presumably this is because =
binds higher than in
- so that expression is equivalent to (r = t) in {a: 1, c: 1}
But in that case - why does using the results of in
require an additional set of parentheses to avoid a syntax error when the result of the expression is used as an argument to a method?
puts(t in {a: 1, c: 1}) # syntax error
puts((t in {a: 1, c: 1}) # false
Especially since this works fine:
puts(case t; in { a: 1, c:1 }; true; else false; end)
Updated by alanwu (Alan Wu) 14 days ago · Edited
- Status changed from Open to Rejected
I'm closing this since I'm pretty sure this isn't a bug. An imperfect explanation follows. Feel free to jump in if anyone has a better explanation.
To understand the precedence, note that in
has a symbolic friend =>
, and much like how or
binds lower than ||
, in
binds lower than =>
. (Runtime behavior of =>
and in
are different, though.)
As for why it requires parentheses in argument context, it's consistent with other single word English keywords such as and
, or
, if
, and unless
:
$ for keyword in and or if unless; do ruby -vc -e "puts(1 $keyword 1)"; done
ruby 3.4.0dev (2024-12-04T21:26:31Z master c0e12bf8d2) +PRISM [arm64-darwin24]
ruby: -e:1: syntax errors found (SyntaxError)
> 1 | puts(1 and 1)
| ^~~ unexpected 'and'; expected a `)` to close the arguments
| ^ unexpected ')', ignoring it
| ^ unexpected ')', expecting end-of-input
2 |
ruby 3.4.0dev (2024-12-04T21:26:31Z master c0e12bf8d2) +PRISM [arm64-darwin24]
ruby: -e:1: syntax errors found (SyntaxError)
> 1 | puts(1 or 1)
| ^~ unexpected 'or'; expected a `)` to close the arguments
| ^ unexpected ')', ignoring it
| ^ unexpected ')', expecting end-of-input
2 |
ruby 3.4.0dev (2024-12-04T21:26:31Z master c0e12bf8d2) +PRISM [arm64-darwin24]
-e:1: warning: literal in condition
ruby: -e:1: syntax errors found (SyntaxError)
> 1 | puts(1 if 1)
| ^~ unexpected 'if'; expected a `)` to close the arguments
| ^ unexpected ')', ignoring it
| ^ unexpected ')', expecting end-of-input
2 |
ruby 3.4.0dev (2024-12-04T21:26:31Z master c0e12bf8d2) +PRISM [arm64-darwin24]
-e:1: warning: literal in condition
ruby: -e:1: syntax errors found (SyntaxError)
> 1 | puts(1 unless 1)
| ^~~~~~ unexpected 'unless'; expected a `)` to close the arguments
| ^ unexpected ')', ignoring it
| ^ unexpected ')', expecting end-of-input
2 |
Allowing these limited examples to work as expected probably causes parsing ambiguity in some other cases, so they're rejected. But I'm no parser expert.
Updated by stephenprater (Stephen Prater) 14 days ago
That works for me - thanks for the explanation.
Updated by mame (Yusuke Endoh) 14 days ago · Edited
As for the limitation of in
, there is a more easy-to-understand explanation. Consider foo(a in 1, 2, 3)
. This is very ambiguous because there are three possible interpretations: foo((a in 1), 2, 3)
, foo((a in 1, 2), 3)
, and foo((a in 1, 2, 3))
. Note that a in 1, 2, 3
returns true when a = [1, 2, 3]
. So parentheses are necessary.
I understand that it is confusing to need double parentheses when you use in
as a simple expression without following commas. But no good solution came to mind.