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

garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-lang.git


The following commit(s) were added to refs/heads/master by this push:
     new df1e91895 Fix TypeUtils.isAssignable() for wildcards with multiple 
upper bounds (#1782)
df1e91895 is described below

commit df1e91895d3d5380efa18a8e8e9d3d7b64ecceab
Author: gaurav kumar pandey <[email protected]>
AuthorDate: Tue Sep 8 17:58:11 2026 +0530

    Fix TypeUtils.isAssignable() for wildcards with multiple upper bounds 
(#1782)
    
    * Fix TypeUtils.isAssignable() for wildcards with multiple upper bounds
    
    * Revert lower bound assignability change to keep PR focused on multiple 
upper bounds
    
    * Add test assertions for multi-bound targets and reverse direction 
assignability
---
 .../apache/commons/lang3/reflect/TypeUtils.java    | 17 +++++---
 .../commons/lang3/reflect/TypeUtilsTest.java       | 50 ++++++++++++++++++++++
 2 files changed, 60 insertions(+), 7 deletions(-)

diff --git a/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java 
b/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java
index 2ff5e4a3b..92dfff7da 100644
--- a/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java
+++ b/src/main/java/org/apache/commons/lang3/reflect/TypeUtils.java
@@ -1229,22 +1229,25 @@ private static boolean isAssignable(final Type type, 
final WildcardType toWildca
                 // if there are assignments for unresolved type variables,
                 // now's the time to substitute them.
                 toBound = substituteTypeVariables(toBound, typeVarAssigns);
-                // each upper bound of the subject type has to be assignable to
-                // each
-                // upper bound of the target type
+                // at least one upper bound of the subject type has to be 
assignable to
+                // each upper bound of the target type
+                boolean satisfied = false;
                 for (final Type bound : upperBounds) {
-                    if (!isAssignable(bound, toBound, typeVarAssigns)) {
-                        return false;
+                    if (isAssignable(bound, toBound, typeVarAssigns)) {
+                        satisfied = true;
+                        break;
                     }
                 }
+                if (!satisfied) {
+                    return false;
+                }
             }
             for (Type toBound : toLowerBounds) {
                 // if there are assignments for unresolved type variables,
                 // now's the time to substitute them.
                 toBound = substituteTypeVariables(toBound, typeVarAssigns);
                 // each lower bound of the target type has to be assignable to
-                // each
-                // lower bound of the subject type
+                // each lower bound of the subject type
                 for (final Type bound : lowerBounds) {
                     if (!isAssignable(toBound, bound, typeVarAssigns)) {
                         return false;
diff --git a/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java 
b/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java
index e25095194..04a2087eb 100644
--- a/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java
@@ -1246,4 +1246,54 @@ void testWrap() {
         assertEquals(String.class, TypeUtils.wrap(String.class).getType());
     }
 
+    @Test
+    void testIsAssignableWildcardWithMultipleUpperBounds() {
+        // ? extends Serializable & Cloneable
+        final WildcardType subject = TypeUtils.wildcardType()
+                .withUpperBounds(Serializable.class, Cloneable.class)
+                .build();
+
+        // ? extends Serializable
+        final WildcardType targetSerializable = TypeUtils.wildcardType()
+                .withUpperBounds(Serializable.class)
+                .build();
+
+        // ? extends Cloneable
+        final WildcardType targetCloneable = TypeUtils.wildcardType()
+                .withUpperBounds(Cloneable.class)
+                .build();
+
+        // ? extends CharSequence
+        final WildcardType targetCharSequence = TypeUtils.wildcardType()
+                .withUpperBounds(CharSequence.class)
+                .build();
+
+        // ? extends Serializable & Cloneable
+        final WildcardType targetSerializableAndCloneable = 
TypeUtils.wildcardType()
+                .withUpperBounds(Serializable.class, Cloneable.class)
+                .build();
+
+        // ? extends Serializable & CharSequence
+        final WildcardType targetSerializableAndCharSequence = 
TypeUtils.wildcardType()
+                .withUpperBounds(Serializable.class, CharSequence.class)
+                .build();
+
+        // Single target bound satisfied
+        assertTrue(TypeUtils.isAssignable(subject, targetSerializable));
+        assertTrue(TypeUtils.isAssignable(subject, targetCloneable));
+        assertTrue(TypeUtils.isAssignable(subject, 
TypeUtils.wildcardType().withUpperBounds(Object.class).build()));
+        assertFalse(TypeUtils.isAssignable(subject, targetCharSequence));
+
+        // Multiple target bounds where all are satisfied
+        assertTrue(TypeUtils.isAssignable(subject, 
targetSerializableAndCloneable));
+        assertTrue(TypeUtils.isAssignable(subject, 
TypeUtils.wildcardType().withUpperBounds(Object.class, 
Serializable.class).build()));
+
+        // Multiple target bounds where only one is satisfied
+        assertFalse(TypeUtils.isAssignable(subject, 
targetSerializableAndCharSequence));
+
+        // Reverse direction: single bound cannot satisfy multiple bounds
+        assertFalse(TypeUtils.isAssignable(targetSerializable, subject));
+        assertFalse(TypeUtils.isAssignable(targetCloneable, subject));
+    }
+
 }

Reply via email to