From b9cd6532c74f27497526680d23f29bd8ed91b817 Mon Sep 17 00:00:00 2001 From: Catarina Gamboa Date: Thu, 8 Oct 2026 10:33:27 +0100 Subject: [PATCH] Upgrade Spoon to 11.5.0 and default the compliance level to 21 Spoon 11.5.0 bundles JDT 3.46, which accepts compliance levels up to 26 (10.4.2's JDT 3.33 stopped at 19). Projects whose pom declares no Java version are now parsed at 21. Spoon 11 models a try-with-resources resource as either a local variable declaration or a read of an existing variable (Java 9 `try (r)`), instead of an implicit copy of the variable's declaration, so the implicit close() is built from that node directly. Co-Authored-By: Claude Opus 5.5 --- liquidjava-verifier/pom.xml | 2 +- .../java/liquidjava/api/ComplianceLevel.java | 9 ++-- .../RefinementTypeChecker.java | 50 +++++-------------- 3 files changed, 20 insertions(+), 41 deletions(-) diff --git a/liquidjava-verifier/pom.xml b/liquidjava-verifier/pom.xml index 33f770ab8..974822727 100644 --- a/liquidjava-verifier/pom.xml +++ b/liquidjava-verifier/pom.xml @@ -257,7 +257,7 @@ 5.10.0 1.3.0 1.12.0 - 10.4.2 + 11.5.0 4.8.17 3.8.0 diff --git a/liquidjava-verifier/src/main/java/liquidjava/api/ComplianceLevel.java b/liquidjava-verifier/src/main/java/liquidjava/api/ComplianceLevel.java index 901824246..d6a63a9c1 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/api/ComplianceLevel.java +++ b/liquidjava-verifier/src/main/java/liquidjava/api/ComplianceLevel.java @@ -14,12 +14,15 @@ */ public final class ComplianceLevel { - /** Highest level accepted by Spoon 10.4.2 (JDT 3.33), also used when no pom declares one */ - public static final int MAX_SUPPORTED = 19; + /** Highest level accepted by Spoon 11.5.0 (JDT 3.46) */ + public static final int MAX_SUPPORTED = 26; + + /** Level used when no pom declares one */ + public static final int DEFAULT = 21; public static int resolve(String... paths) { return Arrays.stream(paths).map(path -> fromPom(new File(path).getAbsoluteFile())).flatMap(Optional::stream) - .max(Integer::compare).map(level -> Math.min(level, MAX_SUPPORTED)).orElse(MAX_SUPPORTED); + .max(Integer::compare).map(level -> Math.min(level, MAX_SUPPORTED)).orElse(DEFAULT); } private static Optional fromPom(File path) { diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java index 954026d33..565776ccc 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java @@ -3,11 +3,8 @@ import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Set; @@ -64,8 +61,6 @@ import spoon.reflect.code.CtVariableRead; import spoon.reflect.code.CtVariableWrite; import spoon.reflect.code.CtWhile; -import spoon.reflect.cu.CompilationUnit; -import spoon.reflect.cu.SourcePosition; import spoon.reflect.declaration.*; import spoon.reflect.factory.Factory; import spoon.reflect.reference.CtExecutableReference; @@ -535,51 +530,32 @@ private boolean canCompleteNormally(CtStatement statement) { @Override public void visitCtTryWithResource(CtTryWithResource tryWithResource) { - // A resource reference (Java 9 `try (r)`) is modelled by Spoon as an implicit copy of r's declaration, - // initializer included, and repeated once per earlier local with the same name, so it is not scanned (that - // would re-run the initializer) and is only closed once - Map> resources = new LinkedHashMap<>(); - for (CtResource resource : tryWithResource.getResources()) { - if (!resource.isImplicit()) - scan(resource); - resources.put(resource.getSimpleName(), resource); - } + // a resource is either a declaration (`try (R r = ...)`) or a reference to an existing variable (Java 9 `try + // (r)`) + List> resources = tryWithResource.getResources(); + scan(resources); scan(tryWithResource.getBody()); // the resources are closed when the body ends, in reverse order, before any catch or finally block runs - List> toClose = new ArrayList<>(resources.values()); - Collections.reverse(toClose); - for (CtResource resource : toClose) { - SourcePosition position = resource.isImplicit() ? getHeaderPosition(tryWithResource) - : resource.getPosition(); - scan(createImplicitClose(resource, tryWithResource, position)); - } + for (int i = resources.size() - 1; i >= 0; i--) + scan(createImplicitClose(resources.get(i), tryWithResource)); scan(tryWithResource.getCatchers()); scan(tryWithResource.getFinalizer()); } - /** Position of {@code try (...)}, without the blocks */ - private SourcePosition getHeaderPosition(CtTryWithResource tryWithResource) { - SourcePosition position = tryWithResource.getPosition(); - CompilationUnit cu = position.getCompilationUnit(); - int end = cu.getOriginalSourceCode().lastIndexOf(')', tryWithResource.getBody().getPosition().getSourceStart()); - if (end < position.getSourceStart()) - return position; - return factory.Core().createSourcePosition(cu, position.getSourceStart(), end, cu.getLineSeparatorPositions()); - } - /** Builds the {@code resource.close()} that Java inserts at the end of a try-with-resources block */ - private CtInvocation createImplicitClose(CtResource resource, CtTryWithResource tryWithResource, - SourcePosition position) { - CtTypeReference type = resource.getType(); + private CtInvocation createImplicitClose(CtResource resource, CtTryWithResource tryWithResource) { + CtExpression target = resource instanceof CtLocalVariable variable + ? factory.Code().createVariableRead(variable.getReference(), false) + : ((CtVariableRead) resource).clone(); + CtTypeReference type = target.getType(); CtExecutableReference close = type.getAllExecutables().stream() .filter(e -> e.getSimpleName().equals("close") && e.getParameters().isEmpty()).findFirst() .orElseGet(() -> factory.Executable().createReference(type, factory.Type().VOID_PRIMITIVE, "close")); - CtExpression target = factory.Code().createVariableRead(resource.getReference(), false); CtInvocation invocation = factory.Code().createInvocation(target, close); invocation.setParent(tryWithResource); - invocation.setPosition(position); - target.setPosition(position); + invocation.setPosition(resource.getPosition()); + target.setPosition(resource.getPosition()); return invocation; }