Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FR: Matching maps with various type #388

Open
jarl-dk opened this issue May 19, 2022 · 2 comments
Open

FR: Matching maps with various type #388

jarl-dk opened this issue May 19, 2022 · 2 comments

Comments

@jarl-dk
Copy link

jarl-dk commented May 19, 2022

Given an actual map like this:

        Map actual = Map.of(
                "k1", Map.of(
                        "k11", "v11",
                        "k12", "v12"),
                "k2", List.of("v21", "v22"),
                "k3", "V3"
        );

I wish/expect to make an assertion with matcher that looks like this:

        assertThat(actual, AllOf.allOf(
                IsMapContaining.hasEntry("k1", AllOf.allOf(
                        IsMapContaining.hasEntry("k11", "v11"),
                        IsMapContaining.hasEntry("k12", "v12"))
                ),
                IsMapContaining.hasEntry("k2", IsIterableContainingInOrder.contains("v21","v22")),
                IsMapContaining.hasEntry("k3", "v3")
                )
        );

But this fails with

    method org.hamcrest.core.AllOf.<T>allOf(java.lang.Iterable<org.hamcrest.Matcher<? super T>>) is not applicable
      (cannot infer type-variable(s) T
        (actual and formal argument lists differ in length))
    method org.hamcrest.core.AllOf.<T>allOf(org.hamcrest.Matcher<? super T>...) is not applicable
      (inferred type does not conform to upper bound(s)
        inferred: java.util.Map<? extends java.lang.String,? extends java.lang.String>
        upper bound(s): java.util.Map<? extends java.lang.String,? extends java.lang.String>,java.util.Map<? extends java.lang.String,? extends org.hamcrest.Matcher<java.lang.Iterable<? extends java.lang.String>>>,java.util.Map<? extends java.lang.String,? extends org.hamcrest.Matcher<java.util.Map<? extends java.lang.String,? extends java.lang.String>>>,java.lang.Object)
    method org.hamcrest.core.AllOf.<T>allOf(org.hamcrest.Matcher<? super T>,org.hamcrest.Matcher<? super T>) is not applicable
      (cannot infer type-variable(s) T
        (actual and formal argument lists differ in length))
    method org.hamcrest.core.AllOf.<T>allOf(org.hamcrest.Matcher<? super T>,org.hamcrest.Matcher<? super T>,org.hamcrest.Matcher<? super T>) is not applicable
      (inferred type does not conform to upper bound(s)
        inferred: java.util.Map<? extends java.lang.String,? extends java.lang.String>
        upper bound(s): java.util.Map<? extends java.lang.String,? extends java.lang.String>,java.util.Map<? extends java.lang.String,? extends org.hamcrest.Matcher<java.lang.Iterable<? extends java.lang.String>>>,java.util.Map<? extends java.lang.String,? extends org.hamcrest.Matcher<java.util.Map<? extends java.lang.String,? extends java.lang.String>>>,java.lang.Object)
    method org.hamcrest.core.AllOf.<T>allOf(org.hamcrest.Matcher<? super T>,org.hamcrest.Matcher<? super T>,org.hamcrest.Matcher<? super T>,org.hamcrest.Matcher<? super T>) is not applicable
      (cannot infer type-variable(s) T
        (actual and formal argument lists differ in length))
    method org.hamcrest.core.AllOf.<T>allOf(org.hamcrest.Matcher<? super T>,org.hamcrest.Matcher<? super T>,org.hamcrest.Matcher<? super T>,org.hamcrest.Matcher<? super T>,org.hamcrest.Matcher<? super T>) is not applicable
      (cannot infer type-variable(s) T
        (actual and formal argument lists differ in length))
    method org.hamcrest.core.AllOf.<T>allOf(org.hamcrest.Matcher<? super T>,org.hamcrest.Matcher<? super T>,org.hamcrest.Matcher<? super T>,org.hamcrest.Matcher<? super T>,org.hamcrest.Matcher<? super T>,org.hamcrest.Matcher<? super T>) is not applicable
      (cannot infer type-variable(s) T
        (actual and formal argument lists differ in length))
dmfs added a commit to saynotobugsorg/confidence that referenced this issue Dec 28, 2023
This provides a Quality similar to `istanceOf` but also taking
`Quality`s of subclasses of the given class. This is unsafe because
there is no guarantee that the testee is actually of that subtype, but
it may be required if the testee is a generic class.

It solves issues like described in
hamcrest/JavaHamcrest#388
dmfs added a commit to saynotobugsorg/confidence that referenced this issue Dec 28, 2023
This provides a Quality similar to `istanceOf` but also taking
`Quality`s of subclasses of the given class. This is unsafe because
there is no guarantee that the testee is actually of that subtype, but
it may be required if the testee is a generic class.

It solves issues like described in
hamcrest/JavaHamcrest#388
dmfs added a commit to saynotobugsorg/confidence that referenced this issue Dec 28, 2023
This provides a Quality similar to `istanceOf` but also taking
`Quality`s of subclasses of the given class. This is unsafe because
there is no guarantee that the testee is actually of that subtype, but
it may be required if the testee is a generic class.

It solves issues like described in
hamcrest/JavaHamcrest#388
dmfs added a commit to saynotobugsorg/confidence that referenced this issue Dec 28, 2023
This provides a Quality similar to `instanceOf` but also taking
`Quality`s of subclasses of the given class. This is unsafe because
there is no guarantee that the testee is actually of that subtype, but
it may be required if the testee is a generic class.

It solves issues like described in
hamcrest/JavaHamcrest#388
@tumbarumba
Copy link
Member

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants