Skip to content

Simplify handling of union types #2330

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

Merged
merged 4 commits into from
May 1, 2017

Conversation

odersky
Copy link
Contributor

@odersky odersky commented Apr 30, 2017

This is an attempt at simplifying the way we handle type unions. The central idea is that type unions should be treated analogous to singleton types: We should eliminate them in inferred types unless
they are explicitly given.

We already do a lot of that in many different contexts and by different means because type unions
have turned out to have problematic interactions with type inference. The aim of this PR is to simplify and generalize what we already do.

odersky added 4 commits April 30, 2017 11:47
Risk of confusing with Types.SingletonType is too high.
The idea is that or-types should never be inferred, so
we widen them

 - when instantiating type variables
 - when computing the type of valdef or defdefs from the rhs.

This is already done in harmonizeUnion. However, harmonizeUnion
had the restrictions that it joined or-types only in Scala2 mode.
This restriction is now dropped.
 - `ctx.harmonizeUnion(tp)` now becomes `tp.widenUnion`.
 - Same order of operations in local type inference of Namer
and type variable instantiation. First, simplify, then widen,
then widenUnion.
@odersky odersky changed the title [WIP] Change handling of or-types Simplify handling of union types Apr 30, 2017
@@ -167,6 +167,7 @@ class tests extends CompilerTest {
@Test def rewrites = compileFile(posScala2Dir, "rewrites", "-rewrite" :: scala2mode)

@Test def pos_t8146a = compileFile(posSpecialDir, "t8146a")(allowDeepSubtypes)
@Test def pos_jon = compileFile(posSpecialDir, "jon")(allowDeepSubtypes)
Copy link
Member

@smarter smarter Apr 30, 2017

Choose a reason for hiding this comment

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

Isn't it surprising that this requires deep subtypes?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did not look too deeply. But SeqFactory has a funky recursive type

SeqFactory[CC[X] <: Seq[X] with GenericTraversableTemplate[X, CC]] extends GenSeqFactory[CC] with TraversableFactory[CC]

so it's not so implausible.

@smarter
Copy link
Member

smarter commented Apr 30, 2017

  • I guess this is a way to get:
val xs3 = mutable.ListBuffer(Right("foo"), Left(0), Right("bar"))

From the collection strawman to be inferred without unions. If so, it'd be good to add it as a testcase.

  • Suggestion: Never widen a union if the widened type would be Any, AnyRef or AnyVal, this is more likely to hide a bug than to be helpful, here's a (simplified) example where not widening discovered a bug in dotty:
sealed trait NegTestState
case class CompFailed() extends NegTestState
case class CompSucceeded() extends NegTestState

object Test { 
  val failureStates /* : List[CompSucceeded | CompFailed.type] */ = List(true, false) map {
    case true => CompSucceeded()
    case false => CompFailed // BUG: should be CompFailed()
  }

  failureStates.exists({
    case CompFailed() => // error: CompFailed is neither subtype
      true               // nor supertype of selector type
    case _ =>
      false
  })
}

@smarter
Copy link
Member

smarter commented Apr 30, 2017

Just updated my example in the previous comment so that it actually compiles with scalac but not with dotty, it was incorrect before.

* re-lubbing it while allowing type parameters to be constrained further.
* Any remaining union types are replaced by their joins.
*
* For instance, if `A` is an unconstrained type variable, then
Copy link
Member

Choose a reason for hiding this comment

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

Incorrect indentation around here.

@@ -830,23 +830,23 @@ object Types {
* def o: Outer
* <o.x.type>.widen = o.C
*/
@tailrec final def widen(implicit ctx: Context): Type = widenSingleton match {
Copy link
Member

Choose a reason for hiding this comment

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

Why was the tailrec annotation removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It was noise. I was against adding it, but it got merged before I could comment.

As I wrote then: Add a @tailrec only if it is important that the method is in fact tail-recursive. Either it is super-performance critical or can potentially recurse deeply. Do not add @tailrec just because the method happens to be tail recursive.

@odersky
Copy link
Contributor Author

odersky commented Apr 30, 2017

@smarter I don't think it's systematic to treat Any or AnyRef differently here. There is code out there that uses Any types pervasively (Spark comes to mind, as do some of our Lisp interpreters). It would be surprising if type inference started to fail.

@odersky
Copy link
Contributor Author

odersky commented Apr 30, 2017

Just updated my example in the previous comment so that it actually compiles with scalac but not with dotty, it was incorrect before.

It also compiles with dotty now 😄

@odersky
Copy link
Contributor Author

odersky commented Apr 30, 2017

From the collection strawman to be inferred without unions. If so, it'd be good to add it as a testcase.

Except that its type will change with the ProductN removal which needs to get in first. In any case we should get to a point where dotty and the collection strawman are always tested together.

@smarter
Copy link
Member

smarter commented Apr 30, 2017

It would be surprising if type inference started to fail.

On the other hand it might motivate people to avoid using Any :).

@smarter
Copy link
Member

smarter commented Apr 30, 2017

scalac even has a -Ywarn-infer-any that some people recommend using.

@odersky
Copy link
Contributor Author

odersky commented May 1, 2017

Yes, I think a warning is the right way to deal with this. We can implement that later.

Copy link
Member

@smarter smarter left a comment

Choose a reason for hiding this comment

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

LGTM

@smarter smarter merged commit 9567fc4 into scala:master May 1, 2017
@smarter
Copy link
Member

smarter commented May 3, 2017

FWIW, timely discussion on the kind of issues I mentioned above: https://www.reddit.com/r/programming/comments/690fpx/when_the_scala_compiler_doesnt_help/

@odersky
Copy link
Contributor Author

odersky commented May 3, 2017 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants