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

Use union of cases as default bound of match types #8085

Closed
wants to merge 4 commits into from

Conversation

OlivierBlanvillain
Copy link
Contributor

Fix lampepfl/dotty-feature-requests#90

This PR implements what @LPTK suggested at the end of the issue, which is to use the union of cases as the bound of match types when no bounds are provided.

Unfortunatly the snippet from the linked issue still doesn't compile, but I think the remaining issue might be unrelated to match type (see #8084).

@@ -1498,7 +1498,10 @@ class Typer extends Namer
val sel1 = typed(tree.selector)
val pt1 = if (bound1.isEmpty) pt else bound1.tpe
val cases1 = tree.cases.mapconserve(typedTypeCase(_, sel1.tpe, pt1))
assignType(cpy.MatchTypeTree(tree)(bound1, sel1, cases1), bound1, sel1, cases1)
val bound2 =
if (tree.bound.isEmpty) TypeTree(cases1.map(_.body.tpe).reduce(_ | _))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this cause problems when body contains references to type variables extracted from a type pattern?
As in: ... match { case List[t] => t }

@OlivierBlanvillain
Copy link
Contributor Author

This change doesn't work well with nested matches. For instance, lets have a look at type Elem from Tuple.scala:

type Elem[X <: Tuple, N <: Int] <: Any = X match {
  case x *: xs =>
    N match {
      case 0 => x
      case S[n1] => Elem[xs, n1]
    }
}

With this change, the inner type N match { ... } gets Any | Elem[? <: Tuple, Int] as its bounds, which create cyclic references at use site.

The main issue here is that we don't have syntax to specify bounds outside of type definition, meaning the above would have to be rewritten as:

type Elem[X <: Tuple, N <: Int] <: Any = X match {
  case x *: xs => Elem0[N, x, xs]
}
type Elem0[N <: Int, X <: Any, XS <: Tuple] <: Any match {
  case 0 => X
  case S[n1] => Elem[XS, n1]
}

Assuming we had better syntax for that, there is some more work needed to make this change viable: the compiler needs the detect cycles and report an error similar to what we have for recursive functions ("recursive match type need explicit bounds"). I think that might bit tricky to implement, given that the cyclic references are only triggered at use site (during subtyping), we would have some additional mechanism when compiling a match type to detect these and error out accordingly.

@LPTK
Copy link
Contributor

LPTK commented Jan 29, 2020

I see, thanks for looking into it @OlivierBlanvillain!

It seems to me that the appropriate thing to do here would be type avoidance of the types being defined while computing the match type bounds, using the bounds actually specified by the user. So instead of Any | Elem[? <: Tuple, Int] we'd get Any | Any in this example.

I don't know whether that could be done easily, though.

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

Successfully merging this pull request may close these issues.

Match types should be subtypes of the union of their case right-hand sides
2 participants