Skip to content

Commit f6682ed

Browse files
committed
Scala parser: map This, InterpolatedString, ForEachLoop, and TypeAscription to proper J types instead of J.Unknown
1 parent d51d23f commit f6682ed

6 files changed

Lines changed: 593 additions & 287 deletions

File tree

rewrite-scala/src/main/java/org/openrewrite/scala/ScalaPrinter.java

Lines changed: 69 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.openrewrite.scala.marker.SObject;
3535
import org.openrewrite.scala.marker.TypeProjection;
3636
import org.openrewrite.scala.marker.ScalaForLoop;
37+
import org.openrewrite.scala.marker.TypeAscription;
3738
import org.openrewrite.scala.marker.UnderscorePlaceholderLambda;
3839
import org.openrewrite.scala.tree.S;
3940

@@ -191,27 +192,7 @@ public J visitAssignmentOperation(J.AssignmentOperation assignOp, PrintOutputCap
191192
afterSyntax(assignOp, p);
192193
return assignOp;
193194
}
194-
195-
@Override
196-
public J visitTypeCast(J.TypeCast typeCast, PrintOutputCapture<P> p) {
197-
beforeSyntax(typeCast, Space.Location.TYPE_CAST_PREFIX, p);
198-
// In Scala, type casts are written as expression.asInstanceOf[Type]
199-
visit(typeCast.getExpression(), p);
200-
p.append(".asInstanceOf");
201-
202-
// Extract the type from the control parentheses
203-
if (typeCast.getClazz() instanceof J.ControlParentheses) {
204-
J.ControlParentheses<?> controlParens = (J.ControlParentheses<?>) typeCast.getClazz();
205-
visitSpace(controlParens.getPrefix(), Space.Location.CONTROL_PARENTHESES_PREFIX, p);
206-
p.append('[');
207-
visitRightPadded(controlParens.getPadding().getTree(), JRightPadded.Location.PARENTHESES, "", p);
208-
p.append(']');
209-
}
210-
211-
afterSyntax(typeCast, p);
212-
return typeCast;
213-
}
214-
195+
215196
@Override
216197
public J visitTry(J.Try tryable, PrintOutputCapture<P> p) {
217198
beforeSyntax(tryable, Space.Location.TRY_PREFIX, p);
@@ -829,21 +810,75 @@ public J visitReturn(J.Return return_, PrintOutputCapture<P> p) {
829810
}
830811

831812
@Override
832-
public J visitForLoop(J.ForLoop forLoop, PrintOutputCapture<P> p) {
833-
// Check if this is a Scala range-based for loop
834-
ScalaForLoop marker = forLoop.getMarkers().findFirst(ScalaForLoop.class).orElse(null);
835-
if (marker != null && marker.getOriginalSource() != null && !marker.getOriginalSource().isEmpty()) {
836-
// Print the original Scala syntax
837-
beforeSyntax(forLoop, Space.Location.FOR_PREFIX, p);
838-
p.append(marker.getOriginalSource());
839-
afterSyntax(forLoop, p);
840-
return forLoop;
813+
public J visitForEachLoop(J.ForEachLoop forEachLoop, PrintOutputCapture<P> p) {
814+
if (forEachLoop.getMarkers().findFirst(ScalaForLoop.class).isPresent()) {
815+
// Scala for-comprehension: for (x <- iterable) body
816+
beforeSyntax(forEachLoop, Space.Location.FOR_EACH_LOOP_PREFIX, p);
817+
p.append("for");
818+
J.ForEachLoop.Control ctrl = forEachLoop.getControl();
819+
visitSpace(ctrl.getPrefix(), Space.Location.FOR_EACH_CONTROL_PREFIX, p);
820+
p.append('(');
821+
822+
// Print the variable (just the name, no type)
823+
JRightPadded<Statement> variable = ctrl.getPadding().getVariable();
824+
Statement varStmt = variable.getElement();
825+
if (varStmt instanceof J.VariableDeclarations) {
826+
J.VariableDeclarations varDecl = (J.VariableDeclarations) varStmt;
827+
visitSpace(varDecl.getPrefix(), Space.Location.VARIABLE_DECLARATIONS_PREFIX, p);
828+
if (!varDecl.getVariables().isEmpty()) {
829+
visit(varDecl.getVariables().get(0).getName(), p);
830+
}
831+
} else {
832+
visit(varStmt, p);
833+
}
834+
835+
// Print "<-" with spaces from the padding
836+
visitSpace(variable.getAfter(), JRightPadded.Location.FOREACH_VARIABLE.getAfterLocation(), p);
837+
p.append("<-");
838+
839+
// Print the iterable
840+
JRightPadded<Expression> iterable = ctrl.getPadding().getIterable();
841+
visit(iterable.getElement(), p);
842+
visitSpace(iterable.getAfter(), JRightPadded.Location.FOREACH_ITERABLE.getAfterLocation(), p);
843+
p.append(')');
844+
845+
// Print the body
846+
visitStatement(forEachLoop.getPadding().getBody(), JRightPadded.Location.FOR_BODY, p);
847+
afterSyntax(forEachLoop, p);
848+
return forEachLoop;
841849
}
842-
// Otherwise use Java syntax
843-
return super.visitForLoop(forLoop, p);
850+
return super.visitForEachLoop(forEachLoop, p);
851+
}
852+
853+
@Override
854+
public J visitTypeCast(J.TypeCast typeCast, PrintOutputCapture<P> p) {
855+
if (typeCast.getMarkers().findFirst(TypeAscription.class).isPresent()) {
856+
// Scala type ascription: expr: Type
857+
beforeSyntax(typeCast, Space.Location.TYPE_CAST_PREFIX, p);
858+
visit(typeCast.getExpression(), p);
859+
if (typeCast.getClazz() instanceof J.ControlParentheses) {
860+
J.ControlParentheses<?> controlParens = (J.ControlParentheses<?>) typeCast.getClazz();
861+
visitSpace(controlParens.getPrefix(), Space.Location.CONTROL_PARENTHESES_PREFIX, p);
862+
p.append(':');
863+
visitRightPadded(controlParens.getPadding().getTree(), JRightPadded.Location.PARENTHESES, "", p);
864+
}
865+
afterSyntax(typeCast, p);
866+
return typeCast;
867+
}
868+
// Existing asInstanceOf handling
869+
beforeSyntax(typeCast, Space.Location.TYPE_CAST_PREFIX, p);
870+
visit(typeCast.getExpression(), p);
871+
p.append(".asInstanceOf");
872+
if (typeCast.getClazz() instanceof J.ControlParentheses) {
873+
J.ControlParentheses<?> controlParens = (J.ControlParentheses<?>) typeCast.getClazz();
874+
visitSpace(controlParens.getPrefix(), Space.Location.CONTROL_PARENTHESES_PREFIX, p);
875+
p.append('[');
876+
visitRightPadded(controlParens.getPadding().getTree(), JRightPadded.Location.PARENTHESES, "", p);
877+
p.append(']');
878+
}
879+
afterSyntax(typeCast, p);
880+
return typeCast;
844881
}
845-
846-
// Override additional methods here for Scala-specific syntax as needed
847882

848883
@Override
849884
public J visitVariableDeclarations(J.VariableDeclarations multiVariable, PrintOutputCapture<P> p) {

rewrite-scala/src/main/java/org/openrewrite/scala/marker/ScalaForLoop.java

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,35 @@
1515
*/
1616
package org.openrewrite.scala.marker;
1717

18-
import lombok.Value;
19-
import lombok.With;
18+
import org.openrewrite.Tree;
2019
import org.openrewrite.marker.Marker;
2120

2221
import java.util.UUID;
2322

2423
/**
25-
* Marker to preserve original Scala for-loop syntax when converting to J.ForLoop.
26-
* This allows us to print the loop back in Scala syntax while still having the
27-
* semantic information of a J.ForLoop for analysis and transformation.
24+
* Marker indicating that a {@link org.openrewrite.java.tree.J.ForEachLoop} represents
25+
* a Scala for-comprehension. The printer uses this to emit Scala syntax
26+
* ({@code for (x <- iterable) body}) instead of Java syntax
27+
* ({@code for (Type x : iterable) body}).
2828
*/
29-
@Value
30-
@With
3129
public class ScalaForLoop implements Marker {
32-
UUID id;
33-
String originalSource;
34-
35-
public static ScalaForLoop create(String originalSource) {
36-
return new ScalaForLoop(UUID.randomUUID(), originalSource);
30+
private final UUID id;
31+
32+
public ScalaForLoop(UUID id) {
33+
this.id = id;
34+
}
35+
36+
@Override
37+
public UUID getId() {
38+
return id;
39+
}
40+
41+
@Override
42+
public ScalaForLoop withId(UUID id) {
43+
return new ScalaForLoop(id);
44+
}
45+
46+
public static ScalaForLoop create() {
47+
return new ScalaForLoop(Tree.randomId());
3748
}
3849
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://docs.moderne.io/licensing/moderne-source-available-license
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.scala.marker;
17+
18+
import org.openrewrite.Tree;
19+
import org.openrewrite.marker.Marker;
20+
21+
import java.util.UUID;
22+
23+
/**
24+
* Marks a {@link org.openrewrite.java.tree.J.TypeCast} that represents a Scala type
25+
* ascription ({@code expr: Type}) rather than a Java-style cast ({@code (Type) expr}).
26+
* <p>
27+
* When this marker is present, the printer emits {@code expr: Type} instead of
28+
* {@code (Type) expr}.
29+
*/
30+
public class TypeAscription implements Marker {
31+
private final UUID id;
32+
33+
public TypeAscription(UUID id) {
34+
this.id = id;
35+
}
36+
37+
@Override
38+
public UUID getId() {
39+
return id;
40+
}
41+
42+
@Override
43+
public TypeAscription withId(UUID id) {
44+
return new TypeAscription(id);
45+
}
46+
47+
public static TypeAscription create() {
48+
return new TypeAscription(Tree.randomId());
49+
}
50+
}

0 commit comments

Comments
 (0)