Skip to content

Commit 915b2a7

Browse files
committed
datatable: Replace wildcard type with its upper bound
When registering steps in Kotlin using Kotlin collections as an argument e.g: ```kotlin @given("orders with an order item and the following attributes:") fun statusFilterOrdersInEs(attributes: Map<String, Map<String, String>>) ``` This argument is translated to Java type as ```java Map<java.lang.String, ? extends java.util.Map<java.lang.String, java.lang.String>> ``` To handle this gracefully the simplified type system used by `DataTable` should understand upper bounds. We do this by replacing the wild card type with its upper bound. We can assume this is safe because when registering a transformer to `type ? extends SomeType` the transformer is guaranteed to produce an object that is an instance of `SomeType`. When transforming a data table to ``? extends SomeType` a transformer that produces `SomeType` is sufficient. This will result in ambiguity between a transformers for `SomeType` and transformers for ``? extends SomeType` but that seems reasonable and might be resolved by using a more specific producer.
1 parent 47fbc09 commit 915b2a7

File tree

2 files changed

+47
-9
lines changed

2 files changed

+47
-9
lines changed

datatable/java/datatable/src/main/java/io/cucumber/datatable/TypeFactory.java

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ private static JavaType constructTypeInner(Type type) {
4949
}
5050

5151
if (type instanceof WildcardType) {
52-
return constructWIldCardType((WildcardType) type);
52+
return constructWildCardType((WildcardType) type);
5353
}
5454

5555
if (type instanceof ParameterizedType) {
@@ -59,8 +59,26 @@ private static JavaType constructTypeInner(Type type) {
5959
return new OtherType(type);
6060
}
6161

62-
private static JavaType constructWIldCardType(WildcardType type) {
63-
// We'll only care about exact matches for now.
62+
private static JavaType constructWildCardType(WildcardType type) {
63+
// For our simplified type system we can safely replace upper bounds
64+
// When registering a transformer to type ? extends SomeType the
65+
// transformer is guaranteed to produce an object that is an instance of
66+
// SomeType.
67+
// When transforming a data table to ? extends SomeType a transformer
68+
// that produces SomeType is sufficient.
69+
// This will result in ambiguity between a transformers for SomeType
70+
// and transformers for ? extends SomeType but that seems reasonable and
71+
// might be resolved by using a more specific producer.
72+
Type[] upperBounds = type.getUpperBounds();
73+
if (upperBounds.length > 0) {
74+
// Not possible in Java. Scala?
75+
if (upperBounds.length > 1) {
76+
throw new IllegalArgumentException("Type contained more then upper lower bound " + type + ". Types may only have a single upper bound.");
77+
}
78+
return constructType(upperBounds[0]);
79+
}
80+
81+
// We'll treat lower bounds as is.
6482
return new OtherType(type);
6583
}
6684

@@ -94,6 +112,11 @@ static String typeName(Type type) {
94112
return type.getTypeName();
95113
}
96114

115+
interface JavaType extends Type {
116+
117+
Type getOriginal();
118+
}
119+
97120
static final class OtherType implements JavaType {
98121

99122
private final Type original;
@@ -218,9 +241,4 @@ public Type getOriginal() {
218241
return original;
219242
}
220243
}
221-
222-
interface JavaType extends Type {
223-
224-
Type getOriginal();
225-
}
226244
}

datatable/java/datatable/src/test/java/io/cucumber/datatable/TypeFactoryTest.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ class TypeFactoryTest {
2121
}.getType();
2222
private static final Type LIST_OF_LIST_OF_OBJECT = new TypeReference<List<List<Object>>>() {
2323
}.getType();
24-
2524
private static final Type LIST_OF_WILD_CARD_NUMBER = new TypeReference<List<? extends Number>>() {
2625
}.getType();
26+
private static final Type LIST_OF_NUMBER = new TypeReference<List<Number>>() {
27+
}.getType();
2728
private static final Type MAP_OF_OBJECT_OBJECT = new TypeReference<Map<Object, Object>>() {
2829
}.getType();
2930

@@ -146,4 +147,23 @@ <T> void type_variables_are_not_allowed() {
146147
"Type contained a type variable T. Types must explicit."
147148
));
148149
}
150+
151+
@Test
152+
void wild_card_types_use_upper_bound_in_equality() {
153+
JavaType javaType = TypeFactory.constructType(LIST_OF_WILD_CARD_NUMBER);
154+
JavaType other = TypeFactory.constructType(LIST_OF_NUMBER);
155+
assertThat(javaType, equalTo(other));
156+
TypeFactory.ListType listType = (TypeFactory.ListType) javaType;
157+
JavaType elementType = listType.getElementType();
158+
assertThat(elementType.getOriginal(), equalTo(Number.class));
159+
}
160+
161+
@Test
162+
void upper_bound_of_wild_card_replaces_wild_card_type() {
163+
JavaType javaType = TypeFactory.constructType(LIST_OF_WILD_CARD_NUMBER);
164+
TypeFactory.ListType listType = (TypeFactory.ListType) javaType;
165+
JavaType elementType = listType.getElementType();
166+
assertThat(elementType.getOriginal(), equalTo(Number.class));
167+
}
168+
149169
}

0 commit comments

Comments
 (0)