From 9e6b9934a9bda587adaaeacaec3c8d3cbd831b11 Mon Sep 17 00:00:00 2001 From: LucySMartin Date: Mon, 25 Mar 2024 15:28:29 +0000 Subject: [PATCH 1/2] First pass of combining multiple union types --- .../src/dotty/tools/dotc/typer/Typer.scala | 79 +++++++++++++++++-- compiler/test-resources/repl/10693 | 23 ++++++ tests/pos/i10693.scala | 19 +++++ 3 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 compiler/test-resources/repl/10693 create mode 100644 tests/pos/i10693.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 0b05bcd078ff..1cb3ce3215e1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3414,20 +3414,85 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def typedType(tree: untpd.Tree, pt: Type = WildcardType, mapPatternBounds: Boolean = false)(using Context): Tree = val tree1 = withMode(Mode.Type) { typed(tree, pt) } + val tree2 = tree1 match + case inferredTree: InferredTypeTree if inferredTree.hasType => + inferredTree.tpe match + case or: OrType => + val res = flattenOr(or) + tree1.withType(res) + case _ => + tree1 + case _ => + tree1 if mapPatternBounds && ctx.mode.is(Mode.Pattern) && !ctx.isAfterTyper then - tree1 match - case tree1: TypeBoundsTree => + tree2 match + case tree2: TypeBoundsTree => // Associate a pattern-bound type symbol with the wildcard. // The bounds of the type symbol can be constrained when comparing a pattern type // with an expected type in typedTyped. The type symbol and the defining Bind node // are eliminated once the enclosing pattern has been typechecked; see `indexPattern` // in `typedCase`. val boundName = WildcardParamName.fresh().toTypeName - val wildcardSym = newPatternBoundSymbol(boundName, tree1.tpe & pt, tree.span) - untpd.Bind(boundName, tree1).withType(wildcardSym.typeRef) - case tree1 => - tree1 - else tree1 + val wildcardSym = newPatternBoundSymbol(boundName, tree2.tpe & pt, tree.span) + untpd.Bind(boundName, tree2).withType(wildcardSym.typeRef) + case tree2 => + tree2 + else tree2 + + private def flattenOr(tp: Type)(using Context): Type = + var options: List[Type] = Nil + var doUpdate: Boolean = false + + def offer(next: Type): Unit = + // By checking at insert time, we will never add an element to the internal state if it is invalidated by + // a later element. Thus as extract time, we only need to validate for those prepended after that point + next match + case OrType(o1, o2) => + offer(o1) + offer(o2) + case _ => + if (!options.exists(prior => next <:< prior)) + options = next :: options + else + doUpdate = true + + offer(tp) + if (doUpdate) + val typesToAdd = options.reverse.tails.flatMap { + case curr :: allLaterAdditions + if !allLaterAdditions.exists(later => curr <:< later) => + Some(curr) + case _ => + doUpdate = true + None + } + + def addHelper(add: Type, orTree: List[Option[Type]], iter: Int = 0): List[Option[Type]] = + orTree match + case None :: more => + var res = Some(add) :: more + for (i <- 1 to iter) { + res = None :: res + } + res + case Some(next) :: more => + addHelper(add | next, more, iter + 1) + case Nil => + var res: List[Option[Type]] = List(Some(add)) + for (i <- 1 to iter) { + res = None :: res + } + res + + val res = typesToAdd.foldLeft[List[Option[Type]]](Nil) { + case (orTree, add) => + addHelper(add, orTree) + }.flatten.reduceLeft(_ | _) + //println(s"${tp.show} ===> ${res.show} (${ctx.tree.show})") + res + else + tp + def typedPattern(tree: untpd.Tree, selType: Type = WildcardType)(using Context): Tree = withMode(Mode.Pattern)(typed(tree, selType)) diff --git a/compiler/test-resources/repl/10693 b/compiler/test-resources/repl/10693 new file mode 100644 index 000000000000..598c054f8b5a --- /dev/null +++ b/compiler/test-resources/repl/10693 @@ -0,0 +1,23 @@ +scala> def test[A, B](a: A, b: B): A | B = a +def test[A, B](a: A, b: B): A | B + +scala> def d0 = test("string", 1) +def d0: String | Int + +scala> def d1 = test(1, "string") +def d1: Int | String + +scala> def d2 = test(d0, d1) +def d2: Int | String + +scala> def d3 = test(d1, d0) +def d3: String | Int + +scala> def d4 = test(d2, d3) +def d4: String | Int + +scala> def d5 = test(d3, d2) +def d5: Int | String + +scala> def d6 = test(d4, d5) +def d6: Int | String \ No newline at end of file diff --git a/tests/pos/i10693.scala b/tests/pos/i10693.scala new file mode 100644 index 000000000000..6c5a24c64cf5 --- /dev/null +++ b/tests/pos/i10693.scala @@ -0,0 +1,19 @@ +object Example { + def test[A, B](a: A, b: B): A | B = a + + val v0 = test("string", 1) + val v1 = test(1, "string") + val v2 = test(v0, v1) + val v3 = test(v1, v0) + val v4 = test(v2, v3) + val v5 = test(v3, v2) + val v6 = test(v4, v5) + + def d0 = test("string", 1) + def d1 = test(1, "string") + def d2 = test(d0, d1) + def d3 = test(d1, d0) + def d4 = test(d2, d3) + def d5 = test(d3, d2) + def d6 = test(d4, d5) +} \ No newline at end of file From 4cf2c86f77073137fc75e7535a54b62754f6cc11 Mon Sep 17 00:00:00 2001 From: Lucy Martin Date: Mon, 25 Mar 2024 17:24:18 +0000 Subject: [PATCH 2/2] Additional handeling for null and nothing types within Or terms --- .../src/dotty/tools/dotc/typer/Typer.scala | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 1cb3ce3215e1..b5c1f4d884c4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3442,6 +3442,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer private def flattenOr(tp: Type)(using Context): Type = var options: List[Type] = Nil var doUpdate: Boolean = false + // Null and Nothing are sub types of everything, and are puled out post-typing via TypeOps, nevertheless, + // we reduce to at most one of each at this stage + var nullRef: Option[Type] = None + var nothingRef: Option[Type] = None def offer(next: Type): Unit = // By checking at insert time, we will never add an element to the internal state if it is invalidated by @@ -3450,6 +3454,16 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case OrType(o1, o2) => offer(o1) offer(o2) + case nothing if nothing.isNothingType => + if (nothingRef.isDefined) + doUpdate = true + else + nothingRef = Some(nothing) + case nul if nul.isNullType => + if (nullRef.isDefined) + doUpdate = true + else + nullRef = Some(nul) case _ => if (!options.exists(prior => next <:< prior)) options = next :: options @@ -3458,7 +3472,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer offer(tp) if (doUpdate) - val typesToAdd = options.reverse.tails.flatMap { + val distinctTypes = options.reverse.tails.map { case curr :: allLaterAdditions if !allLaterAdditions.exists(later => curr <:< later) => Some(curr) @@ -3467,6 +3481,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer None } + val typesToAdd = (Iterator(nothingRef, nullRef) ++ distinctTypes).flatten + def addHelper(add: Type, orTree: List[Option[Type]], iter: Int = 0): List[Option[Type]] = orTree match case None :: more =>