Skip to content

Commit 4c647aa

Browse files
committed
Replace derive4j with a sealed interface and records
derive4j generated BreakBehaviours for one type, BreakBehaviour: four cases, two of them with a keepIndentWhenInlined flag. BreakBehaviour is now a sealed interface whose cases are records, and the factories the call sites used moved onto it. Level picks the case with a switch over record patterns instead of a Cases visitor and caseOf chains, so the compiler checks that every case is handled; the JSON serializer and LevelDelimitedFlatValueDocVisitor switch the same way. Each case keeps derive4j's toString, which the debug visitor prints, and the JSON keeps its shape. Level.BreakImpl's preferBreakingLastInnerLevel never read the flag and now takes no parameter. open-java-format no longer runs the derive4j processor. The 15,747 files of the JDK 21 sources format exactly as before, and the plugins' tests pass against a native image built from this.
1 parent 679e5a7 commit 4c647aa

10 files changed

Lines changed: 194 additions & 186 deletions

File tree

‎buildSrc/src/main/groovy/open-java-format.error-prone-conventions.gradle‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ nullaway {
1616

1717
tasks.withType(JavaCompile).configureEach {
1818
options.errorprone {
19-
// Annotation processor output (derive4j, Immutables) is not ours to fix.
19+
// Annotation processor output (Immutables) is not ours to fix.
2020
excludedPaths = '.*/build/generated/.*'
2121
}
2222
}

‎gradle/libs.versions.toml‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ compileTesting = { module = "com.google.testing.compile:compile-testing", versio
2626
errorprone-core = { module = "com.google.errorprone:error_prone_core", version = "2.50.0" }
2727
errorProneSupport-contrib = { module = "tech.picnic.error-prone-support:error-prone-contrib", version = "0.30.0" }
2828
nullaway = { module = "com.uber.nullaway:nullaway", version = "0.14.2" }
29-
derive4j = { module = "org.derive4j:derive4j", version = "1.1.1" }
30-
derive4j-annotation = { module = "org.derive4j:derive4j-annotation", version = "1.1.1" }
3129
functionaljava = { module = "org.functionaljava:functionaljava", version = "5.0" }
3230
guava = { module = "com.google.guava:guava", version = "33.7.1-jre" }
3331
guava-testlib = { module = "com.google.guava:guava-testlib", version = "33.5.0-jre" }

‎open-java-format/build.gradle‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ dependencies {
2626
compileOnly variantOf(libs.immutables.value) { classifier('annotations') }
2727
annotationProcessor libs.immutables.value
2828

29-
compileOnly libs.derive4j.annotation
30-
annotationProcessor libs.derive4j
3129
implementation libs.jsr305
3230
}
3331

‎open-java-format/src/main/java/com/palantir/javaformat/BreakBehaviour.java‎

Lines changed: 75 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -23,96 +23,99 @@
2323
import com.palantir.javaformat.doc.Doc;
2424
import com.palantir.javaformat.doc.Level;
2525
import java.io.IOException;
26-
import java.io.UncheckedIOException;
27-
import org.derive4j.ArgOption;
28-
import org.derive4j.Data;
2926

30-
@Data(arguments = ArgOption.checkedNotNull)
27+
/**
28+
* How a {@link Level} is broken when it does not fit on one line. Each case's {@code toString()} is what
29+
* {@link com.palantir.javaformat.doc.LevelDelimitedFlatValueDocVisitor} prints for it.
30+
*/
3131
@Immutable
3232
@JsonSerialize(using = BreakBehaviour.Json.class)
33-
public abstract class BreakBehaviour {
34-
public interface Cases<R> {
33+
public sealed interface BreakBehaviour {
3534

36-
R breakThisLevel();
35+
static BreakBehaviour breakThisLevel() {
36+
return new BreakThisLevel();
37+
}
3738

38-
/**
39-
* If the last level is breakable, prefer breaking it if it will keep the rest of this level on line line.
40-
*
41-
* @param keepIndentWhenInlined whether to keep this level's indent when inlined as a recursive level (when
42-
* reached via a previous `preferBreakingLastInnerLevel` whose breakability was
43-
* {@link LastLevelBreakability#CHECK_INNER})
44-
*/
45-
R preferBreakingLastInnerLevel(boolean keepIndentWhenInlined);
39+
static BreakBehaviour preferBreakingLastInnerLevel(boolean keepIndentWhenInlined) {
40+
return new PreferBreakingLastInnerLevel(keepIndentWhenInlined);
41+
}
4642

47-
/**
48-
* Attempt to inline the suffix of this level (which must be a {@link Level} and the last doc), recursing into
49-
* the {@link Level} just before the last {@link Level} (if there is such a level) to see if that can be broken
50-
* instead.
51-
*
52-
* <p>This behaves like {@link #breakThisLevel()} if we couldn't recurse into such an inner level, or if the
53-
* suffix level doesn't fit on the last line.
54-
*/
55-
R inlineSuffix();
43+
static BreakBehaviour inlineSuffix() {
44+
return new InlineSuffix();
45+
}
5646

57-
/**
58-
* Break if by doing so all inner levels then fit on a single line. However, don't break if we can fit in the
59-
* {@link Doc docs} up to the first break (which might be nested inside the next doc if it's a {@link Level}),
60-
* in order to prevent exceeding the maxLength accidentally.
61-
*/
62-
R breakOnlyIfInnerLevelsThenFitOnOneLine(boolean keepIndentWhenInlined);
47+
static BreakBehaviour breakOnlyIfInnerLevelsThenFitOnOneLine(boolean keepIndentWhenInlined) {
48+
return new BreakOnlyIfInnerLevelsThenFitOnOneLine(keepIndentWhenInlined);
6349
}
6450

65-
public abstract <R> R match(Cases<R> cases);
51+
/** Break this level. */
52+
record BreakThisLevel() implements BreakBehaviour {
53+
@Override
54+
public String toString() {
55+
return "breakThisLevel()";
56+
}
57+
}
6658

67-
/** For {@link com.palantir.javaformat.doc.LevelDelimitedFlatValueDocVisitor}. */
68-
@Override
69-
public abstract String toString();
59+
/**
60+
* If the last level is breakable, prefer breaking it if it will keep the rest of this level on line line.
61+
*
62+
* @param keepIndentWhenInlined whether to keep this level's indent when inlined as a recursive level (when
63+
* reached via a previous `preferBreakingLastInnerLevel` whose breakability was
64+
* {@link LastLevelBreakability#CHECK_INNER})
65+
*/
66+
record PreferBreakingLastInnerLevel(boolean keepIndentWhenInlined) implements BreakBehaviour {
67+
@Override
68+
public String toString() {
69+
return "preferBreakingLastInnerLevel(" + keepIndentWhenInlined + ")";
70+
}
71+
}
72+
73+
/**
74+
* Attempt to inline the suffix of this level (which must be a {@link Level} and the last doc), recursing into the
75+
* {@link Level} just before the last {@link Level} (if there is such a level) to see if that can be broken instead.
76+
*
77+
* <p>This behaves like {@link BreakThisLevel} if we couldn't recurse into such an inner level, or if the suffix
78+
* level doesn't fit on the last line.
79+
*/
80+
record InlineSuffix() implements BreakBehaviour {
81+
@Override
82+
public String toString() {
83+
return "inlineSuffix()";
84+
}
85+
}
7086

7187
/**
72-
* This is gross but just wanted to get something working. See https://github.com/derive4j/derive4j/issues/51 for a
73-
* potential better implementation.
88+
* Break if by doing so all inner levels then fit on a single line. However, don't break if we can fit in the
89+
* {@link Doc docs} up to the first break (which might be nested inside the next doc if it's a {@link Level}), in
90+
* order to prevent exceeding the maxLength accidentally.
91+
*
92+
* @param keepIndentWhenInlined whether to keep this level's indent when inlined as a recursive level
7493
*/
75-
static class Json extends JsonSerializer<BreakBehaviour> {
94+
record BreakOnlyIfInnerLevelsThenFitOnOneLine(boolean keepIndentWhenInlined) implements BreakBehaviour {
95+
@Override
96+
public String toString() {
97+
return "breakOnlyIfInnerLevelsThenFitOnOneLine(" + keepIndentWhenInlined + ")";
98+
}
99+
}
76100

101+
/** Writes the case as {@code type} and its flag, if it has one. */
102+
final class Json extends JsonSerializer<BreakBehaviour> {
77103
@Override
78104
public void serialize(BreakBehaviour value, JsonGenerator gen, SerializerProvider serializers)
79105
throws IOException {
80106
gen.writeStartObject();
81-
BreakBehaviours.caseOf(value)
82-
.breakThisLevel(() -> {
83-
try {
84-
gen.writeObjectField("type", "breakThisLevel");
85-
} catch (IOException e) {
86-
throw new UncheckedIOException(e);
87-
}
88-
return null;
89-
})
90-
.preferBreakingLastInnerLevel(keepIndentWhenInlined -> {
91-
try {
92-
gen.writeObjectField("type", "preferBreakingLastInnerLevel");
93-
gen.writeObjectField("keepIndentWhenInlined", keepIndentWhenInlined);
94-
} catch (IOException e) {
95-
throw new UncheckedIOException(e);
96-
}
97-
return null;
98-
})
99-
.inlineSuffix(() -> {
100-
try {
101-
gen.writeObjectField("type", "inlineSuffix");
102-
} catch (IOException e) {
103-
throw new UncheckedIOException(e);
104-
}
105-
return null;
106-
})
107-
.breakOnlyIfInnerLevelsThenFitOnOneLine(keepIndentWhenInlined -> {
108-
try {
109-
gen.writeObjectField("type", "breakOnlyIfInnerLevelsThenFitOnOneLine");
110-
gen.writeObjectField("keepIndentWhenInlined", keepIndentWhenInlined);
111-
} catch (IOException e) {
112-
throw new UncheckedIOException(e);
113-
}
114-
return null;
115-
});
107+
switch (value) {
108+
case BreakThisLevel() -> gen.writeObjectField("type", "breakThisLevel");
109+
case PreferBreakingLastInnerLevel(boolean keepIndentWhenInlined) -> {
110+
gen.writeObjectField("type", "preferBreakingLastInnerLevel");
111+
gen.writeObjectField("keepIndentWhenInlined", keepIndentWhenInlined);
112+
}
113+
case InlineSuffix() -> gen.writeObjectField("type", "inlineSuffix");
114+
case BreakOnlyIfInnerLevelsThenFitOnOneLine(boolean keepIndentWhenInlined) -> {
115+
gen.writeObjectField("type", "breakOnlyIfInnerLevelsThenFitOnOneLine");
116+
gen.writeObjectField("keepIndentWhenInlined", keepIndentWhenInlined);
117+
}
118+
}
116119
gen.writeEndObject();
117120
}
118121
}

‎open-java-format/src/main/java/com/palantir/javaformat/LastLevelBreakability.java‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,25 @@
2020

2121
/**
2222
* How to decide whether to break the last inner level ("this level") of a parent level with
23-
* {@link BreakBehaviour.Cases#preferBreakingLastInnerLevel}.
23+
* {@link BreakBehaviour.PreferBreakingLastInnerLevel}.
2424
*/
2525
public enum LastLevelBreakability {
2626
/**
27-
* Default behaviour. When processing a {@link BreakBehaviour.Cases#preferBreakingLastInnerLevel} chain, if we've
27+
* Default behaviour. When processing a {@link BreakBehaviour.PreferBreakingLastInnerLevel} chain, if we've
2828
* arrived at a level with this breakability, then we should abort the chain.
2929
*/
3030
ABORT,
3131
/**
3232
* Unconditionally allow ending an inline chain at this level, after which this level may be broken as usual, or a
3333
* prefix thereof could be inlined further (if it has the appropriate break behaviour of
34-
* {@link BreakBehaviour.Cases#preferBreakingLastInnerLevel}). This should only be used when you know that the first
34+
* {@link BreakBehaviour.PreferBreakingLastInnerLevel}). This should only be used when you know that the first
3535
* non-Level {@link Doc} inside this level, if you flatten it, is a {@link Break}.
3636
*/
3737
ACCEPT_INLINE_CHAIN,
3838
/**
3939
* Delegate to the {@link LastLevelBreakability} of _this_ level's last inner level. Typically, this will be true if
4040
* this level is not immediately followed by a break (see StartsWithBreakVisitor). Behaves the same as
41-
* {@link #ABORT} if this level is not {@link BreakBehaviour.Cases#preferBreakingLastInnerLevel}.
41+
* {@link #ABORT} if this level is not {@link BreakBehaviour.PreferBreakingLastInnerLevel}.
4242
*/
4343
CHECK_INNER,
4444
/**

‎open-java-format/src/main/java/com/palantir/javaformat/OpenOp.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public abstract class OpenOp extends HasUniqueId implements Op {
4141
*/
4242
@Default
4343
public BreakBehaviour breakBehaviour() {
44-
return BreakBehaviours.breakThisLevel();
44+
return BreakBehaviour.breakThisLevel();
4545
}
4646

4747
/** If it's the last level of its parent, when to inline this level rather than break the parent. */

‎open-java-format/src/main/java/com/palantir/javaformat/PartialInlineability.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
/**
2222
* What are the conditions for a level to be partially inlineable. Partial inlining refers to the behaviour of
23-
* {@link BreakBehaviours#breakOnlyIfInnerLevelsThenFitOnOneLine} where a level is too large to fit on the current line,
23+
* {@link BreakBehaviour.BreakOnlyIfInnerLevelsThenFitOnOneLine} where a level is too large to fit on the current line,
2424
* but a prefix thereof is partially inlined onto the current line.
2525
*
2626
* <p>Specifically, when inlining a level with the above behaviour, the partial inlineability of its first

0 commit comments

Comments
 (0)