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

ClassCastException during pattern matching #2401

Closed
danieldietrich opened this issue Mar 31, 2019 · 11 comments · Fixed by #2403
Closed

ClassCastException during pattern matching #2401

danieldietrich opened this issue Mar 31, 2019 · 11 comments · Fixed by #2403

Comments

@danieldietrich
Copy link
Contributor

danieldietrich commented Mar 31, 2019

Thx @goerge for reporting this bug on Gitter!

Screenshot 2019-03-31 at 19 10 00

Screenshot 2019-03-31 at 19 10 10

@danieldietrich danieldietrich added this to the v0.10.1 milestone Mar 31, 2019
@danieldietrich
Copy link
Contributor Author

danieldietrich commented Mar 31, 2019

Luckily the Match generics are correct.

The pattern matcher $(List.empty()) compares the prototype instance (here: List.empty()) which the actual object that is matched ($() is of type Pattern0):

Screenshot 2019-03-31 at 19 22 42

The error is located in the implementation of List.Nil.equals()

Screenshot 2019-03-31 at 19 26 52

It uses the internal Collections.equals() method:

Screenshot 2019-03-31 at 19 22 28

Which does a check by contents.

Therefore Match matches (because Pattern0.isDefinedAt returns equals == true), then the case is performed, then -----> 💥

I will fix that and check all other locations which could be affected by Collections.equals!

Many thanks! 😊

@danieldietrich
Copy link
Contributor Author

A fix could look like this:

    static <V> boolean equals(Seq<V> source, Object object) {
        if (object == source) {
            return true;
-       } else if (source != null && object instanceof Seq) {
+       } else if (source != null && object != null && source.getClass() == object.getClass()) {
            final Seq<V> seq = (Seq<V>) object;
            return seq.size() == source.size() && areEqual(source, seq);
        } else {
            return false;
        }
    }

Other Collections.equals() overloads are also affected...

@danieldietrich
Copy link
Contributor Author

danieldietrich commented Mar 31, 2019

Looks like changing the Collections.equals() implementatios has a bigger impact on the code-base than I thought 😅. I will get it right!

@goerge
Copy link

goerge commented Mar 31, 2019

@danieldietrich I’m really impressed how fast you react on this issue. Thanks for the good work on vavr.

@danieldietrich
Copy link
Contributor Author

Our notion of collection equality is aligned to that of Scala. All collections that share the same parent type (Seq / Set / Map) are equal (by definition), if they contain the same elements (modulo order).

Screenshot 2019-03-31 at 21 35 25

However, I'm aware of the fact that might be unexpected to Java developers.

Changing List.empty() == Stream.empty() to List.empty() != Stream.empty() might have a big impact to users of VAVR. We can do that in VAVR 1.0.0.

But fixing pattern matching is a must. In order to fix this bug, I will change the implementation of Pattern0 the way, that isDefinedAt also checks assignability in addition to equality.

@goerge
Copy link

goerge commented Mar 31, 2019

I think your / vavrs / scalas notion of collection equality is right.
To be honest I found this issue by accident when using List.empty() to check for emptiness where Seq::isEmpty would have been appropriate.
So with your mentioned fix in isDefinedAt already being available my naive approach would have been right.

danieldietrich added a commit that referenced this issue Mar 31, 2019
@danieldietrich danieldietrich changed the title Wrong type bounds in pattern matching impl ClassCastException during pattern matching Mar 31, 2019
danieldietrich added a commit that referenced this issue Mar 31, 2019
@goerge
Copy link

goerge commented Apr 1, 2019

@danieldietrich Wow. Thanks for the fast fix!

@goerge
Copy link

goerge commented Apr 1, 2019

I'm not sure if the result of this fix was intended.

The following documents the difference between "match" and "equality check":

  @Test
  public void no_match() {
    final Seq empty = Stream.empty();
    final Option<String> result = API.Match(empty).option(
      API.Case(API.$(List.empty()), x -> "Stream.empty() matches List.empty()")
    );

    assertThat(result).isEqualTo(none());
  }

  @Test
  public void eq() {
    final Option<String> result = Option.when(Stream.empty().equals(List.empty()), () -> "Stream.empty() equals List.empty()");

    assertThat(result).isEqualTo(some("Stream.empty() equals List.empty()"));
  }

I'm aware of the fact that this is not a breaking change due to the raised ClassCastException.
Though I doubt that this intuitive.
Is this the same behaviour in Scala?

@danieldietrich
Copy link
Contributor Author

In fact Scala behaves the same... however, I think we should implement collection equality like it is expected by Java developers. I will think about it for VAVR 1.0

Screenshot 2019-04-15 at 14 21 54

@chrylis
Copy link

chrylis commented May 19, 2019

Thanks for your work, Daniel! This bug (I think) is blocking me in 0.10.0 because it causes $(not(instanceOf())) to blow up every time; do you have an estimate for 0.10.1 availability?

@danieldietrich
Copy link
Contributor Author

@chrylis I will release a backward compatible 1.0 within this or the next week.

@danieldietrich danieldietrich modified the milestones: v0.10.1, v0.11.0, v1.0.0 Jul 2, 2019
danieldietrich added a commit that referenced this issue Jul 22, 2019
thadumi pushed a commit to thadumi/vavr that referenced this issue Dec 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants