saynotobugsorg / confidence

A declarative Java Assertion Framework
Apache License 2.0
11 stars 1 forks source link

InstanceOf and raw types #155

Closed dmfs closed 8 months ago

dmfs commented 8 months ago

In theory Confidence should be able to support something like hamcrest/JavaHamcrest#388, like this:

        Map<String, Object> actual = Map.of(
            "k1", Map.of(
                "k11", "v11",
                "k12", "v12"),
            "k2", List.of("v21", "v22"),
            "k3", "v3"
        );

        assertThat(actual, Core.<Map<String, Object>>allOf(
                containsEntry("k1", instanceOf(Map.class, allOf(
                    containsEntry("k11", "v11"),
                    containsEntry("k12", "v12"))
                )),
                containsEntry("k2", instanceOf(Iterable.class, iterates("v21", "v22"))),
                containsEntry("k3", instanceOf(String.class, equalTo("v3")))
            )
        );

The problem, however, is, that instanceOf does not play well with raw types but we can't pass a Class instance of a generic type (because there is no such thing). The problem is that a generic Map type is never a supertype of a raw type.

Changing the InstanceOf constructor from

    public <V extends T> InstanceOf(Class<V> expectation, Quality<? super V> delegate)

to

    public <V extends T, Q extends V> InstanceOf(Class<V> expectation, Quality<? super Q> delegate)

indeed fixes the issue because you can now pass a Quality for any subtype of V but that's not really type safe anymore because there is no guarantee that the tested instance is indeed a Q .

This would compile now (it doesn't without this change) but the test would fail with a ClassCastException

    static class C1 {
        int foo() {
            return 1;
        }
    }

    static class C2 extends C1 {
        int bar() {
            return 2;
        }
    }

    @Test
    void instanceOfTest()
    {
        Object o = new C1();
        assertThat(o, instanceOf(C1.class, has(C2::bar, equalTo(2))));
    }

This needs to be considered carefully because it's not possible to achieve both, supporting instanceOf with raw types and type safe casting, at the same time.

dmfs commented 8 months ago

we could add another Quality called something like unsafeInstanceOf that supports this case but makes it clear that the type-safety guarantees are very weak.