This is an automated email from the ASF dual-hosted git repository.

veithen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ws-axiom.git


The following commit(s) were added to refs/heads/master by this push:
     new 952ef54df Add unit tests for union-checker
952ef54df is described below

commit 952ef54dfadb5e2140faf28d0a0b80d8015d693a
Author: Andreas Veithen-Knowles <[email protected]>
AuthorDate: Mon Jun 15 08:47:38 2026 +0100

    Add unit tests for union-checker
    
    Use the Checker Framework's CheckerFrameworkPerDirectoryTest harness to
    compile small test sources under tests/union/ with the UnionChecker
    attached, asserting the expected diagnostics via inline // :: error:
    comments. Covers subset-based subtyping, instanceof narrowing, the
    @UnknownUnion escape rule, conditional expression LUB/promotion, and
    null/@UnionBottom handling.
    
    Co-authored-by: Claude <[email protected]>
---
 buildutils/union-checker/pom.xml                   | 37 ++++++++++++++
 .../axiom/checker/union/UnionCheckerTest.java      | 37 ++++++++++++++
 .../union-checker/tests/union/ConditionalLub.java  | 52 ++++++++++++++++++++
 .../tests/union/ConditionalPromotion.java          | 52 ++++++++++++++++++++
 .../tests/union/InstanceofNarrowing.java           | 56 ++++++++++++++++++++++
 .../union-checker/tests/union/NullAssignment.java  | 32 +++++++++++++
 .../union-checker/tests/union/Subtyping.java       | 44 +++++++++++++++++
 .../tests/union/UnknownUnionEscape.java            | 48 +++++++++++++++++++
 8 files changed, 358 insertions(+)

diff --git a/buildutils/union-checker/pom.xml b/buildutils/union-checker/pom.xml
index 51ad2dd6e..8fb2af930 100644
--- a/buildutils/union-checker/pom.xml
+++ b/buildutils/union-checker/pom.xml
@@ -44,5 +44,42 @@
             <groupId>com.google.auto.service</groupId>
             <artifactId>auto-service-annotations</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.checkerframework</groupId>
+            <artifactId>framework-test</artifactId>
+            <version>${checkerframework.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <!-- The tests invoke the javac compiler API directly, 
which requires access
+                         to javac internals not exported by the jdk.compiler 
module. -->
+                    <argLine>
+                        --add-opens 
jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
+                        --add-opens 
jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
+                        --add-opens 
jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
+                        --add-opens 
jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
+                        --add-opens 
jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
+                        --add-opens 
jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
+                        --add-opens 
jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
+                        --add-opens 
jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
+                        --add-opens 
jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
+                        --add-opens 
jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
+                        --add-opens 
jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED
+                    </argLine>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
 </project>
diff --git 
a/buildutils/union-checker/src/test/java/org/apache/axiom/checker/union/UnionCheckerTest.java
 
b/buildutils/union-checker/src/test/java/org/apache/axiom/checker/union/UnionCheckerTest.java
new file mode 100644
index 000000000..ceb216d50
--- /dev/null
+++ 
b/buildutils/union-checker/src/test/java/org/apache/axiom/checker/union/UnionCheckerTest.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.axiom.checker.union;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Runs the {@link UnionChecker} against the test sources in {@code 
tests/union}. */
+public class UnionCheckerTest extends CheckerFrameworkPerDirectoryTest {
+
+    public UnionCheckerTest(List<File> testFiles) {
+        super(testFiles, UnionChecker.class, "union", "-Anomsgtext");
+    }
+
+    @Parameters
+    public static String[] getTestDirs() {
+        return new String[] {"union"};
+    }
+}
diff --git a/buildutils/union-checker/tests/union/ConditionalLub.java 
b/buildutils/union-checker/tests/union/ConditionalLub.java
new file mode 100644
index 000000000..e53aa0d7a
--- /dev/null
+++ b/buildutils/union-checker/tests/union/ConditionalLub.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.axiom.checker.union.Union;
+
+/**
+ * Tests for the least upper bound of two {@code @Union} qualifiers at a 
conditional expression
+ * ({@code UnionQualifierHierarchy#leastUpperBoundWithElements}).
+ */
+class ConditionalLub {
+
+    static class A {}
+
+    static class B {}
+
+    void acceptAOrB(@Union(types = {A.class, B.class}) Object o) {}
+
+    void acceptAOnly(@Union(types = {A.class}) Object o) {}
+
+    // The lub of @Union({A}) and @Union({B}) is @Union({A, B}).
+    void lub(
+            boolean flag,
+            @Union(types = {A.class}) Object a,
+            @Union(types = {B.class}) Object b) {
+        acceptAOrB(flag ? a : b);
+    }
+
+    // @Union({A, B}) is not a subtype of @Union({A}).
+    void lubNotNarrower(
+            boolean flag,
+            @Union(types = {A.class}) Object a,
+            @Union(types = {B.class}) Object b) {
+        // :: error: (argument)
+        acceptAOnly(flag ? a : b);
+    }
+}
diff --git a/buildutils/union-checker/tests/union/ConditionalPromotion.java 
b/buildutils/union-checker/tests/union/ConditionalPromotion.java
new file mode 100644
index 000000000..839e2ad1b
--- /dev/null
+++ b/buildutils/union-checker/tests/union/ConditionalPromotion.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.axiom.checker.union.Union;
+
+/**
+ * Tests for promoting the type of a conditional expression to {@code 
@Union(types = {...})} when
+ * one branch is {@code @UnknownUnion} but its erased type is a subtype of a 
union member
+ * ({@code UnionTreeAnnotator}).
+ */
+class ConditionalPromotion {
+
+    // sb has erased type StringBuilder, a subtype of CharSequence, so the 
conditional
+    // expression is promoted to @Union({String, CharSequence}), regardless of 
which branch it
+    // appears in.
+    @Union(types = {String.class, CharSequence.class}) Object unknownSecond(
+            boolean flag,
+            @Union(types = {String.class, CharSequence.class}) Object data, 
StringBuilder sb) {
+        return flag ? sb : data;
+    }
+
+    @Union(types = {String.class, CharSequence.class}) Object unknownFirst(
+            boolean flag,
+            @Union(types = {String.class, CharSequence.class}) Object data, 
StringBuilder sb) {
+        return flag ? data : sb;
+    }
+
+    // other has erased type Object, which is not a subtype of String or 
CharSequence, so no
+    // promotion happens and the conditional expression's type remains 
@UnknownUnion, which is not
+    // a subtype of the declared @Union return type.
+    @Union(types = {String.class, CharSequence.class}) Object noPromotion(
+            boolean flag, @Union(types = {String.class, CharSequence.class}) 
Object data, Object other) {
+        // :: error: (return)
+        return flag ? other : data;
+    }
+}
diff --git a/buildutils/union-checker/tests/union/InstanceofNarrowing.java 
b/buildutils/union-checker/tests/union/InstanceofNarrowing.java
new file mode 100644
index 000000000..0b9679ba2
--- /dev/null
+++ b/buildutils/union-checker/tests/union/InstanceofNarrowing.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.axiom.checker.union.Union;
+
+/** Tests for {@code instanceof}-based narrowing ({@code UnionTransfer}). */
+class InstanceofNarrowing {
+
+    void acceptStringOrInteger(@Union(types = {String.class, Integer.class}) 
Object o) {}
+
+    void acceptIntegerOnly(@Union(types = {Integer.class}) Object o) {}
+
+    // x instanceof String || x instanceof Integer narrows x to 
@Union({String, Integer}).
+    void or(Object x) {
+        if (x instanceof String || x instanceof Integer) {
+            acceptStringOrInteger(x);
+        }
+    }
+
+    // The equivalent if/else if chain narrows x the same way in each branch.
+    void elseIf(Object x) {
+        if (x instanceof String) {
+            acceptStringOrInteger(x);
+        } else if (x instanceof Integer) {
+            acceptStringOrInteger(x);
+        } else {
+            // :: error: (argument)
+            acceptStringOrInteger(x);
+        }
+    }
+
+    // x instanceof String narrows x to @Union({String}), which is not a 
subtype of
+    // @Union({Integer}).
+    void mismatch(Object x) {
+        if (x instanceof String) {
+            // :: error: (argument)
+            acceptIntegerOnly(x);
+        }
+    }
+}
diff --git a/buildutils/union-checker/tests/union/NullAssignment.java 
b/buildutils/union-checker/tests/union/NullAssignment.java
new file mode 100644
index 000000000..8d274c090
--- /dev/null
+++ b/buildutils/union-checker/tests/union/NullAssignment.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.axiom.checker.union.Union;
+
+/** Tests that {@code null} ({@code @UnionBottom}) is assignable to any {@code 
@Union} type. */
+class NullAssignment {
+
+    @Union(types = {String.class, Integer.class}) Object field = null;
+
+    void acceptStringOrInteger(@Union(types = {String.class, Integer.class}) 
Object o) {}
+
+    void test() {
+        acceptStringOrInteger(null);
+    }
+}
diff --git a/buildutils/union-checker/tests/union/Subtyping.java 
b/buildutils/union-checker/tests/union/Subtyping.java
new file mode 100644
index 000000000..7b291e508
--- /dev/null
+++ b/buildutils/union-checker/tests/union/Subtyping.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.axiom.checker.union.Union;
+
+/** Tests for {@code @Union} subset-based subtyping ({@code 
UnionQualifierHierarchy}). */
+class Subtyping {
+
+    void acceptNarrow(@Union(types = {String.class}) Object o) {}
+
+    void acceptWide(@Union(types = {String.class, Integer.class}) Object o) {}
+
+    // @Union({String}) is a subtype of @Union({String, Integer}).
+    void narrowToWide(@Union(types = {String.class}) Object narrow) {
+        acceptWide(narrow);
+    }
+
+    // @Union({String, Integer}) is not a subtype of @Union({String}).
+    void wideToNarrow(@Union(types = {String.class, Integer.class}) Object 
wide) {
+        // :: error: (argument)
+        acceptNarrow(wide);
+    }
+
+    // Equal sets are subtypes of each other.
+    void sameSet(@Union(types = {Integer.class, String.class}) Object wide) {
+        acceptWide(wide);
+    }
+}
diff --git a/buildutils/union-checker/tests/union/UnknownUnionEscape.java 
b/buildutils/union-checker/tests/union/UnknownUnionEscape.java
new file mode 100644
index 000000000..529253f04
--- /dev/null
+++ b/buildutils/union-checker/tests/union/UnknownUnionEscape.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.axiom.checker.union.Union;
+
+/**
+ * Tests for the {@code @UnknownUnion} escape rule, which allows an 
unconstrained value to be used
+ * where {@code @Union(types = {...})} is expected if its erased type is a 
subtype of one of the
+ * union members ({@code UnionTypeHierarchy}).
+ */
+class UnknownUnionEscape {
+
+    void acceptStringOrCharSequence(@Union(types = {String.class, 
CharSequence.class}) Object o) {}
+
+    void acceptIntegerOnly(@Union(types = {Integer.class}) Object o) {}
+
+    // String is itself one of the union members.
+    void exactMember(String s) {
+        acceptStringOrCharSequence(s);
+    }
+
+    // StringBuilder is a subtype of CharSequence, a union member.
+    void subtypeOfMember(StringBuilder sb) {
+        acceptStringOrCharSequence(sb);
+    }
+
+    // StringBuilder is not a subtype of Integer, the only union member.
+    void notAMember(StringBuilder sb) {
+        // :: error: (argument)
+        acceptIntegerOnly(sb);
+    }
+}

Reply via email to