Skip to content

Match types used in methods cause issues with inheritance #8666

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

Closed
felixmulder opened this issue Apr 4, 2020 · 5 comments · Fixed by #12153
Closed

Match types used in methods cause issues with inheritance #8666

felixmulder opened this issue Apr 4, 2020 · 5 comments · Fixed by #12153
Assignees
Milestone

Comments

@felixmulder
Copy link
Contributor

felixmulder commented Apr 4, 2020

Minimized code

class Foo[A, B]()

type FooSnd[X] = X match
  case Foo[_, b] => b

trait Bar[A]:
  def bar(h: FooSnd[A]): Int

val foo: Bar[Foo[String, Int]] = new Bar[Foo[String, Int]]:
  def bar(h: FooSnd[Foo[String, Int]]) = h

but it works fine if bar is pulled top-level:

- val foo: Bar[Foo[String, Int]] = new Bar[Foo[String, Int]]:
-   def bar(h: FooSnd[Foo[String, Int]]) = h
+ def bar(h: FooSnd[Foo[String, Int]]) = h

Then it compiles as expected.

Output

[error] -- Error: /Users/fixel/.src/fix-me-olivier.scala:19:6
[error] 19 |  def bar(h: FooSnd[Foo[String, Int]]) = h
[error]    |      ^
[error]    |error overriding method bar in trait Bar of type (h: FooSnd[Foo[String, Int]]): Int;
[error]    |  method bar of type (h: Int): Int has incompatible type

Expectation

Not sure - the error message needs improvement.

@felixmulder
Copy link
Contributor Author

I guess this really messes with inheritance, since the overridden method is not necessarily the same as the super - so I'm not surprised that it doesn't compile. But the error message is perhaps not the best.

It's a bit sad though that it wouldn't be possible to use this in given 😢

@felixmulder felixmulder changed the title Type reduction for match types in traits does not seem to happen Match types used in methods cause issues with inheritance Apr 4, 2020
@smarter
Copy link
Member

smarter commented Apr 4, 2020

since the overridden method is not necessarily the same as the super

That's not a new problem: you can override a method that takes an abstract A with a method that takes an Int if you instantiate A to Int. Erasure takes care of adding bridges to preserve overriding relationships in the generated code.

@smarter
Copy link
Member

smarter commented Apr 4, 2020

It seems that the fundamental problem here is that we don't have enough subtyping rules for match types:

scala> type Foo[A] = A match { case Int => String; case _ => Double }

scala> def foo[T](x: Foo[T]) = x
def foo[T](x: Foo[T]): Foo[T]

scala> foo("")
1 |foo("")
  |    ^^
  |    Found:    ("" : String)
  |    Required: Foo[T]
  |
  |    where:    T is a type variable

scala> foo("": Foo[Int])
1 |foo("": Foo[Int])
  |    ^^^^^^^^^^^^
  |    Found:    String
  |    Required: Foo[T]
  |
  |    where:    T is a type variable

scala> foo[Int]("")
val res0: String = ""

I think what we should do when doing the subtype check:
String <:< Foo[?T]
is to check all branches of Foo and find the branch that matches (so here the first one), and record the extra subtyping constraints from that match (?T <: Int). If multiple branches match, we need to be more careful, but we should be able to reuse the logic from TypeComparer#either

@bishabosha
Copy link
Member

#8667 for another example

@smarter
Copy link
Member

smarter commented Apr 10, 2020

I think what we should do when doing the subtype check:
String <:< Foo[?T]
is to check all branches of Foo

I looked into this a bit more and I don't think that's realistic: match types can be so complicated than properly checking if a type is a subset of a match type given some type variable is going to be extremely expensive in general. On the other hand, it's really easy to check that Foo[Int] <:< Foo[?T] can return true if we set ?T := Int. The problem is that right now, we are too eager to reduce match types:

scala> type Foo[A] = A match { case Int => String }

scala> val a: Foo[Int] = ""
val a: String = ""

@OlivierBlanvillain Why is the type of a inferred to be String instead of Foo[Int] here ? We shouldn't mess with what the user wrote, type aliases don't have this problem:

scala> type Id[A] = A
// defined alias type Id[A] = A

scala> val x: Id[Int] = 1
val x: Id[Int] = 1

@odersky odersky self-assigned this Apr 13, 2020
odersky added a commit to dotty-staging/dotty that referenced this issue Apr 20, 2021
This fixes several erasure related things, such as signature, or erasure itself.

Fixes scala#8666
odersky added a commit to dotty-staging/dotty that referenced this issue Apr 20, 2021
This fixes several erasure related things, such as signature, or erasure itself.

Fixes scala#8666
michelou pushed a commit to michelou/scala3 that referenced this issue Apr 21, 2021
This fixes several erasure related things, such as signature, or erasure itself.

Fixes scala#8666
@Kordyjan Kordyjan added this to the 3.0.1 milestone Aug 2, 2023
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.

6 participants