Scala parser: wrap non-Expression J trees in S.StatementExpression at visitUnknown fallbacks#7385
Merged
jkschneider merged 1 commit intomainfrom Apr 15, 2026
Merged
Conversation
ScalaTreeVisitor: mirror the else branch's expression-wrapping fallback that the then branch already had, so `if (x) a else b` with expression operands parses (closes asymmetry that threw UnsupportedOperationException). Add `case j: J => new S.StatementExpression(Tree.randomId(), j)` fallbacks at ~25 visitUnknown sites where the parser previously crashed on any J that wasn't exactly an Expression/Statement — covering visitWhileDo body/cond, visitForDo body/iterable, visitReturn, visitThrow, visitMatchImpl selector, visitAssign lhs/rhs, visitInfixOp / visitBinaryOperation / visitInfixMethodCall operands, visitPrefixOp / visitPostfixOp operand, visitParentheses inner, visitTyped, visitTypeApply asInstanceOf/isInstanceOf, visitAppliedTypeTree args, method call target, ArrayAccess array/index, NewArray / NewArrayWithType elements, NewClassWithArgs constructor args, annotation args, tuple elements. Try body/finalizer now also accept Statement, matching the existing ParsedTry behavior. ScalaParser.sourcePathFromSourceText: was hard-coded to "file.scala" so every source in a multi-file test collided on the same path and clobbered each other on print round-trip. Extract package + class name via a shared derivedRelativePath helper (reusing logic from parse()) so each source gets a distinct path. 7 new regression tests covering if-else-as-expression (literal/ident operands), return/throw/assign with if-rhs, binary/tuple/paren with if-operand, while-with-unit-body, and multi-file distinct paths. Full rewrite-scala suite passes.
5d9b68b to
3fd7156
Compare
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
thenbranch's expression-wrapping fallback invisitIf'selsebranch soif (x) a else bwith expression operands (e.g.if (flag) "yes" else "no") parses without throwingUnsupportedOperationException.case j: J => new S.StatementExpression(Tree.randomId(), j)fallbacks at ~25visitUnknownsites so Scala idioms where an expression appears in an expression-required slot (but maps to a non-ExpressionJ) no longer crash the parser. Affected visitors include:visitWhileDo,visitForDo,visitReturn,visitThrow,visitMatchImpl,visitAssign,visitInfixOp/visitBinaryOperation/visitInfixMethodCall,visitPrefixOp/visitPostfixOp,visitParentheses,visitSelect,visitTyped,visitTypeApply(asInstanceOf/isInstanceOf),visitAppliedTypeTree, method-call target, ArrayAccess, NewArray/NewArrayWithType, NewClassWithArgs, annotation args, tuple elements. Try body/finalizer also now acceptStatement(matching existingParsedTryhandling).ScalaParser.sourcePathFromSourceText— was hard-coded to"file.scala", causing multi-file tests to collide on a single path and garble each other on print round-trip. Extracts package + class name via a sharedderivedRelativePathhelper (logic reused fromparse()).Motivation
Integration-testing prethink quality recipes against Scala sources surfaced two concrete blockers:
UnsupportedOperationException: Unmapped Scala AST node: If at [116..141] source=if (flag) "yes" else "no"— theelsepvisit invisitIfwas missing the expression-wrapping fallback thatthenpalready had.src/main/scala/file.scala, producing garbled print output.The
elsefix exposed that the same defensive pattern was missing at many other sites. The fallback is mechanical: ifvisitTreereturns aJthat isn't exactly anExpression(e.g. aStatementlikeJ.Ifused as a value in Scala), wrap it viaS.StatementExpression(which implements bothExpressionandStatement). This keeps existing behavior for the happy path and replaces a crash with a round-trippable LST in edge cases.Test plan
ControlFlowTestandCompilationUnitTest:ifElseExpressionLiteralOperands,ifElseExpressionIdentifierOperandsreturnWithIfExpression,assignmentWithIfExpressionbinaryOpWithIfExpressionOperand,tupleWithIfExpressionElements,throwWithIfExpressionwhileWithUnitBodymultipleFilesGetDistinctPathsrewrite-scalatest suite (~400 tests across 30+ classes) passes with 0 regressions.