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));
+ }
+
}