Skip to content

FR: Matching maps with various type #388

Open

Description

@jarl-dk

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))

Activity

  1. tumbarumba commented on Sep 21, 2024

    @tumbarumba
    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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions