Skip to content

Reject modifier conditionals in if/unless predicates#4120

Merged
kddnewton merged 2 commits into
ruby:mainfrom
kai-matsudate:reject-modifier-conditional-in-predicate
Jun 1, 2026
Merged

Reject modifier conditionals in if/unless predicates#4120
kddnewton merged 2 commits into
ruby:mainfrom
kai-matsudate:reject-modifier-conditional-in-predicate

Conversation

@kai-matsudate
Copy link
Copy Markdown
Contributor

@kai-matsudate kai-matsudate commented Jun 1, 2026

Fixes Bug #22089.

Prism accepted a statement modifier (if/unless/while/until) in an if/unless/elsif condition, while parse.y rejects it.

if a if b then end   # parse.y: SyntaxError, Prism: accepted

Those conditions were parsed with a binding-power floor of PM_BINDING_POWER_MODIFIER. That floor is inclusive: statement modifiers have left binding power PM_BINDING_POWER_MODIFIER, so they were consumed as part of the condition. while/until conditions already use PM_BINDING_POWER_COMPOSITION and reject this syntax.

This raises the floor to PM_BINDING_POWER_MODIFIER + 1, which keeps and/or and tighter operators in the condition but excludes statement modifiers, matching parse.y.

while/until use PM_BINDING_POWER_COMPOSITION for the same effect because there is currently no precedence between MODIFIER and COMPOSITION. I used PM_BINDING_POWER_MODIFIER + 1 to match the existing + 1 floor idiom (PM_BINDING_POWER_DEFINED + 1, PM_BINDING_POWER_MATCH + 1) and state the boundary directly. I can switch this to PM_BINDING_POWER_COMPOSITION if keeping the paths aligned is preferred.

Added an error fixture under test/prism/errors/.

Prism accepted a modifier conditional as the predicate of an `if`/`unless`
(and `elsif`), while parse.y rejects it:

    if a if b then end      # parse.y: SyntaxError, prism: accepted
    unless a unless b then end

The `if`/`unless`/`elsif` predicate was parsed with a floor of
`PM_BINDING_POWER_MODIFIER`, which is inclusive of the modifier
conditionals (`if`/`unless`/`while`/`until`, left binding power
`PM_BINDING_POWER_MODIFIER`), so they were absorbed into the predicate.
`while`/`until` predicates already use `PM_BINDING_POWER_COMPOSITION` and
reject these correctly.

Raise the floor to `PM_BINDING_POWER_MODIFIER + 1` so the predicate still
absorbs `and`/`or` (and tighter operators) but excludes the modifier
conditionals, matching parse.y and aligning `if`/`unless` with
`while`/`until`.
@kddnewton
Copy link
Copy Markdown
Collaborator

Yeah this is great, I'm good with almost all the changes here. I think we should go with PM_BINDING_POWER_COMPOSITION though. The + 1 is really to indicate associativity, as opposed to the blanket binding power.

@kai-matsudate
Copy link
Copy Markdown
Contributor Author

The two failing cargo:test jobs look unrelated to this change.
I'm not familiar with the Rust/wasm, but it looks like a wasi-sdk toolchain issue rather than the parser.
rust-lang/rust#146843 seems related.
All other jobs pass.

The `+ 1` form is meant to express associativity, not a binding-power
floor; use the named constant instead, matching while/until conditions.
@kddnewton kddnewton marked this pull request as ready for review June 1, 2026 17:14
@kai-matsudate
Copy link
Copy Markdown
Contributor Author

Yeah this is great, I'm good with almost all the changes here. I think we should go with PM_BINDING_POWER_COMPOSITION though. The + 1 is really to indicate associativity, as opposed to the blanket binding power.

@kddnewton Thanks for the advice, switched to PM_BINDING_POWER_COMPOSITION in 399a641.

Copy link
Copy Markdown
Collaborator

@kddnewton kddnewton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@kddnewton kddnewton merged commit 53d1ee7 into ruby:main Jun 1, 2026
67 of 69 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants