Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ public interface SwitchEntry extends Comparable<SwitchEntry>, Element {
*/
RecordPattern patternVariable();

/**
* @return the patterns after the first one of a label that carries several (JEP 456: {@code case A _, B _ ->}),
* in source order; empty otherwise. {@link #patternVariable()} is the first. Java allows several patterns in one
* label only when none of them binds a variable, so these never introduce one.
*/
default List<RecordPattern> otherPatterns() {
return List.of();
}

/**
* @return the arm's code as a block (wrapping {@link #statement()} when it is a single statement).
*/
Expand Down Expand Up @@ -76,6 +85,10 @@ interface Builder extends Element.Builder<Builder> {
@Fluent
Builder setPatternVariable(RecordPattern patternVariable);

/** A pattern after the first one of the label, see {@link SwitchEntry#otherPatterns()}. */
@Fluent
Builder addOtherPattern(RecordPattern pattern);

@Fluent
Builder setWhenExpression(Expression whenExpression);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ public void visit(Predicate<Element> predicate) {
for (SwitchEntry entry : entries) {
entry.conditions().forEach(e -> e.visit(predicate));
if (entry.patternVariable() != null) entry.patternVariable().visit(predicate);
entry.otherPatterns().forEach(o -> o.visit(predicate)); // case A _, B _ -> (JEP 456)
entry.whenExpression().visit(predicate);
entry.statement().visit(predicate);
i++;
Expand All @@ -173,6 +174,7 @@ public void visit(Visitor visitor) {
for (SwitchEntry entry : entries) {
entry.conditions().forEach(e -> e.visit(visitor));
if (entry.patternVariable() != null) entry.patternVariable().visit(visitor);
entry.otherPatterns().forEach(o -> o.visit(visitor)); // case A _, B _ -> (JEP 456)
entry.whenExpression().visit(visitor);
visitor.startSubBlock(i);
entry.statement().visit(visitor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
public class SwitchEntryImpl implements SwitchEntry {
private final List<Expression> conditions;
private final RecordPattern patternVariable;
private final List<RecordPattern> otherPatterns;
private final Expression whenExpression;
private final Statement statement;
private final Source source;
Expand All @@ -52,8 +53,19 @@ public SwitchEntryImpl(List<Comment> comments,
RecordPattern patternVariable,
Expression whenExpression,
Statement statement) {
this(comments, source, conditions, patternVariable, List.of(), whenExpression, statement);
}

public SwitchEntryImpl(List<Comment> comments,
Source source,
List<Expression> conditions,
RecordPattern patternVariable,
List<RecordPattern> otherPatterns,
Expression whenExpression,
Statement statement) {
this.conditions = conditions;
this.patternVariable = patternVariable;
this.otherPatterns = List.copyOf(otherPatterns);
this.whenExpression = whenExpression;
this.statement = statement;
this.comments = comments == null ? List.of() : List.copyOf(comments);
Expand All @@ -67,12 +79,13 @@ public int compareTo(SwitchEntry o) {

@Override
public SwitchEntry withStatement(Statement statement) {
return new SwitchEntryImpl(comments, source, conditions, patternVariable, whenExpression, statement);
return new SwitchEntryImpl(comments, source, conditions, patternVariable, otherPatterns, whenExpression,
statement);
}

@Override
public int complexity() {
return (patternVariable != null ? 1 : 0)
return (patternVariable != null ? 1 : 0) + otherPatterns.size()
+ conditions.stream().mapToInt(Expression::complexity).sum()
+ whenExpression.complexity() + statement().complexity();
}
Expand All @@ -92,6 +105,11 @@ public RecordPattern patternVariable() {
return patternVariable;
}

@Override
public List<RecordPattern> otherPatterns() {
return otherPatterns;
}

@Override
public Block statementAsBlock() {
if (statement instanceof Block b) return b;
Expand Down Expand Up @@ -157,6 +175,11 @@ public OutputBuilder print(Qualification qualification) {
outputBuilder.add(SymbolEnum.COMMA);
}
outputBuilder.add(patternVariable.print(qualification));
// 'case A _, B _ ->' (JEP 456): printing the first pattern only turned it into 'case A _ ->', which
// compiles and no longer matches B
for (RecordPattern other : otherPatterns) {
outputBuilder.add(SymbolEnum.COMMA).add(other.print(qualification));
}
if (whenExpression != null && !whenExpression.isEmpty()) {
outputBuilder.add(SpaceEnum.ONE).add(KeywordImpl.WHEN).add(SpaceEnum.ONE)
.add(whenExpression.print(qualification));
Expand All @@ -173,15 +196,18 @@ public SwitchEntry translate(TranslationMap translationMap) {
.collect(translationMap.toList(conditions));
RecordPattern tPattern = patternVariable == null ? null
: patternVariable.translate(translationMap);
List<RecordPattern> tOthers = otherPatterns.stream().map(o -> o.translate(translationMap))
.collect(translationMap.toList(otherPatterns));
Expression tWhen = whenExpression == null ? null : whenExpression.translate(translationMap);
List<Statement> tStatements = statement.translate(translationMap);
Statement tStatement = tStatements.isEmpty() ? null : tStatements.getFirst();
if (tConditions == conditions && tPattern == patternVariable && tWhen == whenExpression
&& tStatement == statement) {
if (tConditions == conditions && tPattern == patternVariable && tOthers == otherPatterns
&& tWhen == whenExpression && tStatement == statement) {
return this;
}
if (tStatement == null) return null; // a way for the entry to disappear
SwitchEntry result = new SwitchEntryImpl(comments, source, tConditions, tPattern, tWhen, tStatement);
SwitchEntry result = new SwitchEntryImpl(comments, source, tConditions, tPattern, tOthers, tWhen,
tStatement);
return translationMap.postTranslationHandler(this, result);
}

Expand All @@ -190,6 +216,7 @@ public SwitchEntry rewire(InfoMapView infoMap) {
return new SwitchEntryImpl(comments, source,
conditions.stream().map(e -> e.rewire(infoMap)).toList(),
patternVariable == null ? null : (RecordPattern) patternVariable.rewire(infoMap),
otherPatterns.stream().map(o -> (RecordPattern) o.rewire(infoMap)).toList(),
whenExpression.rewire(infoMap), statement.rewire(infoMap));
}

Expand All @@ -202,6 +229,7 @@ public Source source() {
public void visit(Predicate<Element> predicate) {
if (predicate.test(this)) {
if (patternVariable != null) patternVariable.visit(predicate);
otherPatterns.forEach(o -> o.visit(predicate));
whenExpression.visit(predicate);
}
}
Expand All @@ -228,7 +256,9 @@ public Stream<Variable> variableStreamDescend() {
// the case labels read variables too (#66): a Java enum label is a static field read, and K2 lowers a
// subject-less Kotlin `when` to a switch over `true` whose labels are arbitrary boolean expressions
Stream<Variable> s0 = conditions.stream().flatMap(Expression::variableStreamDescend);
Stream<Variable> s1 = patternVariable == null ? Stream.of() : patternVariable.variableStreamDescend();
Stream<Variable> s1 = Stream.concat(
patternVariable == null ? Stream.of() : patternVariable.variableStreamDescend(),
otherPatterns.stream().flatMap(RecordPattern::variableStreamDescend));
return Stream.concat(s0, Stream.concat(s1,
Stream.concat(whenExpression.variableStreamDescend(), statement.variableStreamDescend())));
}
Expand All @@ -239,14 +269,19 @@ public Stream<Element.TypeReference> typesReferenced(Predicate<Element> predicat
// the case labels ('case Kind.CONST ->') reference types too; without them, a type used only in a
// label is invisible to import maintenance
Stream<Element.TypeReference> s0 = conditions.stream().flatMap(c -> c.typesReferenced(predicate));
Stream<Element.TypeReference> s1 = patternVariable == null ? Stream.of() : patternVariable.typesReferenced(predicate);
// every pattern of 'case A _, B _ ->', not only the first: B used to be invisible here, and trino's
// 'case VarcharType _, CharType _' lost its import of CharType to a dead-code removal
Stream<Element.TypeReference> s1 = Stream.concat(
patternVariable == null ? Stream.of() : patternVariable.typesReferenced(predicate),
otherPatterns.stream().flatMap(o -> o.typesReferenced(predicate)));
return Stream.concat(s0, Stream.concat(s1,
Stream.concat(whenExpression.typesReferenced(predicate), statement.typesReferenced(predicate))));
}

public static class EntryBuilderImpl extends ElementImpl.Builder<Builder> implements Builder {
private final List<Expression> conditions = new ArrayList<>();
private RecordPattern patternVariable;
private final List<RecordPattern> otherPatterns = new ArrayList<>();
private Expression whenExpression;
private Statement statement;

Expand All @@ -268,6 +303,12 @@ public Builder setPatternVariable(RecordPattern patternVariable) {
return this;
}

@Override
public Builder addOtherPattern(RecordPattern pattern) {
this.otherPatterns.add(pattern);
return this;
}

@Override
public Builder setWhenExpression(Expression whenExpression) {
this.whenExpression = whenExpression;
Expand All @@ -277,7 +318,7 @@ public Builder setWhenExpression(Expression whenExpression) {
@Override
public SwitchEntry build() {
return new SwitchEntryImpl(List.copyOf(comments), source,
List.copyOf(conditions), patternVariable, whenExpression, statement);
List.copyOf(conditions), patternVariable, otherPatterns, whenExpression, statement);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ public void visit(Predicate<Element> predicate) {
for (SwitchEntry entry : entries) {
entry.conditions().forEach(e -> e.visit(predicate));
if (entry.patternVariable() != null) entry.patternVariable().visit(predicate);
entry.otherPatterns().forEach(o -> o.visit(predicate)); // case A _, B _ -> (JEP 456)
entry.whenExpression().visit(predicate);
entry.statement().visit(predicate);
}
Expand All @@ -102,6 +103,7 @@ public void visit(Visitor visitor) {
for (SwitchEntry entry : entries) {
entry.conditions().forEach(e -> e.visit(visitor));
if (entry.patternVariable() != null) entry.patternVariable().visit(visitor);
entry.otherPatterns().forEach(o -> o.visit(visitor)); // case A _, B _ -> (JEP 456)
entry.whenExpression().visit(visitor);
visitor.startSubBlock(i);
entry.statement().visit(visitor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,7 @@ private static List<OutputBuilder> entryLabels(SwitchEntry entry, Qualification
List<Expression> conditions = entry.conditions();
// a pattern's arm, case Field field ->, has no condition
if (entry.patternVariable() != null) {
return List.of(caseLabel(null, entry.patternVariable(), entry.whenExpression(), q));
return List.of(caseLabel(entry, q));
}
if (conditions.isEmpty() || conditions.stream().allMatch(CSharpStatementPrinter::isDefault)) {
return List.of(new OutputBuilderImpl().add(KeywordImpl.DEFAULT).add(SymbolEnum.COLON_LABEL));
Expand Down Expand Up @@ -686,7 +686,7 @@ private static OutputBuilder switchArm(SwitchEntry entry, ParameterizedType type
List<Expression> conditions = entry.conditions();
if (entry.patternVariable() != null) {
// a record pattern's arm has no condition
head = pattern(entry.patternVariable(), q);
head = entryPattern(entry, q);
} else if (conditions.isEmpty() || conditions.stream().allMatch(CSharpStatementPrinter::isDefault)) {
head = text("_");
} else {
Expand All @@ -700,6 +700,38 @@ private static OutputBuilder switchArm(SwitchEntry entry, ParameterizedType type
.add(armValue(entry.statement(), type, q));
}

/** {@code case T t when (c):} for a pattern arm, every pattern of the label included. */
private static OutputBuilder caseLabel(SwitchEntry entry, Qualification q) {
OutputBuilder b = new OutputBuilderImpl().add(KeywordImpl.CASE).add(SpaceEnum.ONE).add(entryPattern(entry, q));
if (entry.whenExpression() != null && !entry.whenExpression().isEmpty()) {
b.add(SpaceEnum.ONE).add(CSharpKeyword.WHEN).add(SpaceEnum.ONE)
.add(CSharpExpressionPrinter.print(entry.whenExpression(), q));
}
return b.add(SymbolEnum.COLON_LABEL);
}

/**
* The pattern of an arm. A label with several patterns ({@code case String _, Integer _ ->}, JEP 456) is C#'s
* {@code or} pattern, {@code string or int}: printing the first alone compiled and no longer matched an Integer.
* Java allows several patterns only when none binds a variable, and C# allows none under {@code or}, so each
* type pattern is written as its type alone.
*/
private static OutputBuilder entryPattern(SwitchEntry entry, Qualification q) {
if (entry.otherPatterns().isEmpty()) return pattern(entry.patternVariable(), q);
OutputBuilder b = new OutputBuilderImpl();
List<RecordPattern> all = new ArrayList<>();
all.add(entry.patternVariable());
all.addAll(entry.otherPatterns());
for (int i = 0; i < all.size(); i++) {
if (i > 0) b.add(SpaceEnum.ONE).add(text("or")).add(SpaceEnum.ONE);
RecordPattern p = all.get(i);
b.add(p.localVariable() != null
? text(CSharpTypeName.pattern(p.localVariable().parameterizedType(), q))
: pattern(p, q));
}
return b;
}

private static OutputBuilder joinOr(List<Expression> conditions, Qualification q) {
OutputBuilder b = new OutputBuilderImpl();
for (int i = 0; i < conditions.size(); i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2657,4 +2657,33 @@ public void innerClasses() {
// an inner class that does not use its enclosing instance does not take it
contains(cs, "internal sealed class Plain { internal int One() => 1; }");
}

/**
* A case label with several patterns (JEP 456), {@code case String _, Integer _ ->}: C#'s {@code or} pattern,
* {@code string or int}, in a switch statement and in a switch expression. Translating only the first pattern
* compiles and no longer matches an Integer.
*/
@Test
public void multiPatternCase() {
String cs = translate("Kinds", """
package a;
class Kinds {
static String statement(Object o) {
switch (o) {
case String _, Integer _ -> { return "known"; }
default -> { }
}
return "?";
}
static String expression(Object o) {
return switch (o) {
case String _, Integer _ -> "known";
default -> "?";
};
}
}
""");
contains(cs, "case string or int: return \"known\";");
contains(cs, "string or int => \"known\"");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace A;
internal sealed class Kinds {
internal static string Statement(object o) {
switch (o) {
case string or int: return "known";
default: break;
}
return "?";
}
internal static string Expression(object o) => o switch { string or int => "known", _ => "?" };
}
Original file line number Diff line number Diff line change
Expand Up @@ -692,11 +692,23 @@ private static boolean isDefault(SwitchEntry e) {
&& e.conditions().stream().allMatch(c -> c.isEmpty() || c instanceof NullConstant);
}

/** {@code case CmdLineException c ->}: a type pattern, Java 21's. Not a record's deconstruction. */
/**
* {@code case CmdLineException c ->}: a type pattern, Java 21's. Not a record's deconstruction. With several
* patterns ({@code case A _, B _ ->}, JEP 456) every one of them has to be a type pattern: Kotlin's
* {@code is A, is B} tests the type only, which a deconstruction does not reduce to.
*/
private static boolean typePattern(SwitchEntry e) {
return e.patternVariable() != null
&& (e.patternVariable().patterns() == null || e.patternVariable().patterns().isEmpty())
&& (e.patternVariable().localVariable() != null || e.patternVariable().recordType() != null);
return e.patternVariable() != null && isTypePattern(e.patternVariable())
&& e.otherPatterns().stream().allMatch(KotlinStatementPrinter::isTypePattern);
}

private static boolean isTypePattern(io.codelaser.maddi.cst.api.element.RecordPattern p) {
return (p.patterns() == null || p.patterns().isEmpty())
&& (p.localVariable() != null || p.recordType() != null);
}

private static ParameterizedType patternType(io.codelaser.maddi.cst.api.element.RecordPattern p) {
return p.localVariable() != null ? p.localVariable().parameterizedType() : p.recordType();
}

private static OutputBuilder whenEntry(SwitchEntry e, Expression selector, boolean intSelector, Qualification q) {
Expand All @@ -712,6 +724,11 @@ private static OutputBuilder whenEntry(SwitchEntry e, Expression selector, boole
}
b.add(new TextImpl("is " + KotlinTypeName.of(type.withNullable(
io.codelaser.maddi.cst.api.type.NullableState.NONNULL), q)));
// case A _, B _ -> (JEP 456): is A, is B. Printing only the first compiled and stopped matching B.
for (io.codelaser.maddi.cst.api.element.RecordPattern other : e.otherPatterns()) {
b.add(SymbolEnum.COMMA).add(new TextImpl("is " + KotlinTypeName.of(patternType(other).withNullable(
io.codelaser.maddi.cst.api.type.NullableState.NONNULL), q)));
}
if (e.whenExpression() != null && !e.whenExpression().isEmpty()) {
// case X x when cond: Kotlin's guard
b.add(SpaceEnum.ONE).add(KotlinKeyword.IF).add(SpaceEnum.ONE).add(KotlinExpressionPrinter.print(e.whenExpression(), q));
Expand Down
Loading
Loading