Open jarl-dk opened 2 years ago
I was looking at this issue, and I'm struggling to work out how to fix this. I tried to recreate the example as described above, but pulling out all the individual matchers so that I could inspect the exact types. Here's what I ended up with:
public void testNestedMatchers() {
String k1 = "k1";
Map<String, String> v1 = Map.of(
"k11", "v11",
"k12", "v12");
String k2 = "k2";
List<String> v2 = List.of("v21", "v22");
String k3 = "k3";
String v3 = "v3";
Map<String, Object> actual = Map.of(
k1, v1,
k2, v2,
k3, v3
);
Matcher<? super String> k1Matcher = equalTo("k1");
assertThat(k1, k1Matcher);
Matcher<Map<? extends String, ?>> kv11Matcher = hasEntry("k11", "v11");
assertThat(v1, kv11Matcher);
Matcher<Map<? extends String, ?>> kv12Matcher = hasEntry("k12", "v12");
assertThat(v1, kv12Matcher);
Matcher<Map<? extends String, ? extends String>> v1Matcher = allOf(kv11Matcher, kv12Matcher);
assertThat(v1, v1Matcher);
Matcher<? super Map<? extends String, ?>> kv1Matcher = hasEntry(k1Matcher, v1Matcher);
// ^ error: incompatible types: inference variable V has incompatible bounds
// Matcher<? super Map<? extends String, ?>> kv1Matcher = hasEntry(k1Matcher, v1Matcher);
// ^
// upper bounds: Map<? extends String,? extends String>,Object
// lower bounds: Object
// where V,K are type-variables:
// V extends Object declared in method <K,V>hasEntry(Matcher<? super K>,Matcher<? super V>)
// K extends Object declared in method <K,V>hasEntry(Matcher<? super K>,Matcher<? super V>)
Matcher<Iterable<? extends String>> v2Matcher = contains("v21", "v22");
assertThat(v2, v2Matcher);
Matcher<Map<? extends String, ?>> kv2Matcher = hasEntry(equalTo("k2"), v2Matcher);
// ^ error: incompatible types: inference variable V has incompatible bounds
// Matcher<Map<? extends String, ?>> kv2Matcher = hasEntry(equalTo("k2"), v2Matcher);
// ^
// equality constraints: Object
// upper bounds: Iterable<? extends String>,Object
// where V,K are type-variables:
// V extends Object declared in method <K,V>hasEntry(Matcher<? super K>,Matcher<? super V>)
// K extends Object declared in method <K,V>hasEntry(Matcher<? super K>,Matcher<? super V>)
assertThat(actual, kv2Matcher);
Matcher<Map<? extends String, ?>> kv3Matcher = hasEntry("k3", "v3");
assertThat(actual, kv3Matcher);
Matcher<Map<? extends String, ?>> fullMatcher = allOf(kv1Matcher, kv2Matcher, kv3Matcher);
assertThat(actual, fullMatcher);
}
Expanding out the types like this, I get the compilation errors as noted in the comments above. This is the definition of hasEntry:
public static <K, V> Matcher<Map<? extends K, ? extends V>> hasEntry(Matcher<? super K> keyMatcher, Matcher<? super V> valueMatcher) {
return new IsMapContaining<>(keyMatcher, valueMatcher);
}
To me, it looks like this is following the PECS rule (see https://stackoverflow.com/questions/2723397/what-is-pecs-producer-extends-consumer-super). In this instance, the method arguments are acting as consumers of values (hence the super
), while the matcher as a whole is acting against a collection from which values are produced (hence the extends
).
I recently put in a fix for the IsIterableContaining
matcher without a problem using this guide, but this doesn't seem to be the same issue. I'm thinking that the extra complexity of having the 2 type variables (K
and V
) doesn't give enough constraints to the compiler.
Does anyone have any suggestions?
Given an actual map like this:
I wish/expect to make an assertion with matcher that looks like this:
But this fails with