From 2e26337770ec8af1a4ceeb2ba5e1d164f1733ab2 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Sat, 3 Oct 2026 14:19:12 +0100 Subject: [PATCH 1/2] Resolve inherited public constants in field reads Co-authored-by: Codex --- .../ImageWriteParamRefinements.java | 16 ++++++ .../inherited_constant_correct/Repro.java | 13 +++++ .../liquidjava/utils/StaticConstants.java | 8 +-- .../liquidjava/utils/StaticConstantsTest.java | 49 +++++++++++++++++++ 4 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 liquidjava-example/src/main/java/testSuite/classes/inherited_constant_correct/ImageWriteParamRefinements.java create mode 100644 liquidjava-example/src/main/java/testSuite/classes/inherited_constant_correct/Repro.java create mode 100644 liquidjava-verifier/src/test/java/liquidjava/utils/StaticConstantsTest.java diff --git a/liquidjava-example/src/main/java/testSuite/classes/inherited_constant_correct/ImageWriteParamRefinements.java b/liquidjava-example/src/main/java/testSuite/classes/inherited_constant_correct/ImageWriteParamRefinements.java new file mode 100644 index 00000000..a4abd66f --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/classes/inherited_constant_correct/ImageWriteParamRefinements.java @@ -0,0 +1,16 @@ +package testSuite.classes.inherited_constant_correct; + +import liquidjava.specification.ExternalRefinementsFor; +import liquidjava.specification.Refinement; +import liquidjava.specification.StateRefinement; +import liquidjava.specification.StateSet; + +@StateSet({"start", "explicit"}) +@ExternalRefinementsFor("javax.imageio.ImageWriteParam") +public interface ImageWriteParamRefinements { + @StateRefinement(to = "mode == 2 ? explicit(this) : start(this)") + void setCompressionMode(@Refinement("_ >= 0 && _ <= 3") int mode); + + @StateRefinement(from = "explicit(this)") + void setCompressionQuality(@Refinement("_ >= 0.0 && _ <= 1.0") float quality); +} diff --git a/liquidjava-example/src/main/java/testSuite/classes/inherited_constant_correct/Repro.java b/liquidjava-example/src/main/java/testSuite/classes/inherited_constant_correct/Repro.java new file mode 100644 index 00000000..de848ae6 --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/classes/inherited_constant_correct/Repro.java @@ -0,0 +1,13 @@ +package testSuite.classes.inherited_constant_correct; + +import javax.imageio.ImageIO; +import javax.imageio.ImageWriteParam; +import javax.imageio.plugins.jpeg.JPEGImageWriteParam; + +public class Repro { + public static void main(String[] args) { + ImageWriteParam p = ImageIO.getImageWritersByFormatName("jpeg").next().getDefaultWriteParam(); + p.setCompressionMode(JPEGImageWriteParam.MODE_EXPLICIT); + p.setCompressionQuality(0.5f); + } +} diff --git a/liquidjava-verifier/src/main/java/liquidjava/utils/StaticConstants.java b/liquidjava-verifier/src/main/java/liquidjava/utils/StaticConstants.java index 0e8657b5..4bcbd99e 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/utils/StaticConstants.java +++ b/liquidjava-verifier/src/main/java/liquidjava/utils/StaticConstants.java @@ -30,8 +30,8 @@ private StaticConstants() { * *

* Tries the source AST first ({@link CtLiteral} initializer in Spoon's model), then reflection via - * {@link CtFieldReference#getActualField()} + {@link #readStaticFinal}. Returns {@code null} if the field isn't - * static-final, has a non-literal initializer, or any lookup step fails. + * {@link Class#getField(String)} + {@link #readStaticFinal} so inherited public constants are included. Returns + * {@code null} if the field isn't static-final, has a non-literal initializer, or any lookup step fails. * * @see #resolve(String, String, CtElement) sibling for refinement-string {@code Type.CONST} references */ @@ -43,8 +43,8 @@ public static Object resolve(CtFieldReference ref) { if (v != null) return v; try { - return ref.getActualField()instanceof Field jf ? readStaticFinal(jf) : null; - } catch (RuntimeException | LinkageError ignored) { + return readStaticFinal(ref.getDeclaringType().getActualClass().getField(ref.getSimpleName())); + } catch (NoSuchFieldException | RuntimeException | LinkageError ignored) { // Spoon throws SpoonClassNotFoundException; reflection can throw LinkageError. Fall through. return null; } diff --git a/liquidjava-verifier/src/test/java/liquidjava/utils/StaticConstantsTest.java b/liquidjava-verifier/src/test/java/liquidjava/utils/StaticConstantsTest.java new file mode 100644 index 00000000..81bc28db --- /dev/null +++ b/liquidjava-verifier/src/test/java/liquidjava/utils/StaticConstantsTest.java @@ -0,0 +1,49 @@ +package liquidjava.utils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; + +import spoon.Launcher; +import spoon.reflect.code.CtFieldRead; +import spoon.reflect.visitor.filter.TypeFilter; + +class StaticConstantsTest { + @Test + void resolvesInheritedLibraryConstant() { + assertEquals(2, resolveRead( + "class Repro { int mode = javax.imageio.plugins.jpeg.JPEGImageWriteParam.MODE_EXPLICIT; }")); + } + + @Test + void resolvesInheritedInterfaceConstant() { + assertEquals(0, resolveRead("class Repro { int alignment = javax.swing.JLabel.CENTER; }")); + } + + @Test + void resolvesDeclaredLibraryConstant() { + assertEquals(2, resolveRead("class Repro { int mode = javax.imageio.ImageWriteParam.MODE_EXPLICIT; }")); + } + + @Test + void resolvesPrivateSourceConstant() { + assertEquals(3, resolveRead("class Repro { private static final int LIMIT = 3; int value = LIMIT; }")); + } + + @Test + void rejectsMutableSourceField() { + assertNull(resolveRead("class Repro { static int LIMIT = 3; int value = LIMIT; }")); + } + + @Test + void rejectsInstanceSourceField() { + assertNull(resolveRead("class Repro { final int LIMIT = 3; int value = LIMIT; }")); + } + + private static Object resolveRead(String source) { + var type = Launcher.parseClass(source); + CtFieldRead read = type.getElements(new TypeFilter<>(CtFieldRead.class)).get(0); + return StaticConstants.resolve(read.getVariable()); + } +} From 2c58bda5cdfc74948076e2837660b1450050a8d3 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Sat, 3 Oct 2026 14:22:44 +0100 Subject: [PATCH 2/2] Align inherited constant regression with example tests Co-authored-by: Codex --- .../inherited_constant_correct/Repro.java | 13 ----- .../SimpleTest.java | 14 ++++++ .../liquidjava/utils/StaticConstantsTest.java | 49 ------------------- 3 files changed, 14 insertions(+), 62 deletions(-) delete mode 100644 liquidjava-example/src/main/java/testSuite/classes/inherited_constant_correct/Repro.java create mode 100644 liquidjava-example/src/main/java/testSuite/classes/inherited_constant_correct/SimpleTest.java delete mode 100644 liquidjava-verifier/src/test/java/liquidjava/utils/StaticConstantsTest.java diff --git a/liquidjava-example/src/main/java/testSuite/classes/inherited_constant_correct/Repro.java b/liquidjava-example/src/main/java/testSuite/classes/inherited_constant_correct/Repro.java deleted file mode 100644 index de848ae6..00000000 --- a/liquidjava-example/src/main/java/testSuite/classes/inherited_constant_correct/Repro.java +++ /dev/null @@ -1,13 +0,0 @@ -package testSuite.classes.inherited_constant_correct; - -import javax.imageio.ImageIO; -import javax.imageio.ImageWriteParam; -import javax.imageio.plugins.jpeg.JPEGImageWriteParam; - -public class Repro { - public static void main(String[] args) { - ImageWriteParam p = ImageIO.getImageWritersByFormatName("jpeg").next().getDefaultWriteParam(); - p.setCompressionMode(JPEGImageWriteParam.MODE_EXPLICIT); - p.setCompressionQuality(0.5f); - } -} diff --git a/liquidjava-example/src/main/java/testSuite/classes/inherited_constant_correct/SimpleTest.java b/liquidjava-example/src/main/java/testSuite/classes/inherited_constant_correct/SimpleTest.java new file mode 100644 index 00000000..02a6e512 --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/classes/inherited_constant_correct/SimpleTest.java @@ -0,0 +1,14 @@ +package testSuite.classes.inherited_constant_correct; + +import javax.imageio.ImageIO; +import javax.imageio.ImageWriteParam; +import javax.imageio.plugins.jpeg.JPEGImageWriteParam; + +public class SimpleTest { + + public static void main(String[] args) { + ImageWriteParam param = ImageIO.getImageWritersByFormatName("jpeg").next().getDefaultWriteParam(); + param.setCompressionMode(JPEGImageWriteParam.MODE_EXPLICIT); + param.setCompressionQuality(0.5f); + } +} diff --git a/liquidjava-verifier/src/test/java/liquidjava/utils/StaticConstantsTest.java b/liquidjava-verifier/src/test/java/liquidjava/utils/StaticConstantsTest.java deleted file mode 100644 index 81bc28db..00000000 --- a/liquidjava-verifier/src/test/java/liquidjava/utils/StaticConstantsTest.java +++ /dev/null @@ -1,49 +0,0 @@ -package liquidjava.utils; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -import org.junit.jupiter.api.Test; - -import spoon.Launcher; -import spoon.reflect.code.CtFieldRead; -import spoon.reflect.visitor.filter.TypeFilter; - -class StaticConstantsTest { - @Test - void resolvesInheritedLibraryConstant() { - assertEquals(2, resolveRead( - "class Repro { int mode = javax.imageio.plugins.jpeg.JPEGImageWriteParam.MODE_EXPLICIT; }")); - } - - @Test - void resolvesInheritedInterfaceConstant() { - assertEquals(0, resolveRead("class Repro { int alignment = javax.swing.JLabel.CENTER; }")); - } - - @Test - void resolvesDeclaredLibraryConstant() { - assertEquals(2, resolveRead("class Repro { int mode = javax.imageio.ImageWriteParam.MODE_EXPLICIT; }")); - } - - @Test - void resolvesPrivateSourceConstant() { - assertEquals(3, resolveRead("class Repro { private static final int LIMIT = 3; int value = LIMIT; }")); - } - - @Test - void rejectsMutableSourceField() { - assertNull(resolveRead("class Repro { static int LIMIT = 3; int value = LIMIT; }")); - } - - @Test - void rejectsInstanceSourceField() { - assertNull(resolveRead("class Repro { final int LIMIT = 3; int value = LIMIT; }")); - } - - private static Object resolveRead(String source) { - var type = Launcher.parseClass(source); - CtFieldRead read = type.getElements(new TypeFilter<>(CtFieldRead.class)).get(0); - return StaticConstants.resolve(read.getVariable()); - } -}