From 01e715d9a8e33960291b78796dc989a23333ed6e Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 26 Mar 2024 18:48:37 +0100 Subject: [PATCH 1/3] Re-lub also hard union types in simplify Simplify had some elaborate condition that prevented hard union types to be recomputed with a lub. I am not sure why that was. In the concrete scenario of i10693.scala, we had an explicitly union result type `B | A` where `A` and `B` are type parameters. So that is a hard union type. Then `A` was instantiated by `Int | String` and `B` was instantiated by `String | Int`. Re-forming the lub of that union would have eliminated one pair, but since the union type was hard tyat was not done. On the other hand I see no reason why hard unions should not be re-lubbed. Hard unions are about preventing the widening of or types with a join. I don't see a connection with avoiding re-lubbing. Fixes #10693 --- compiler/src/dotty/tools/dotc/core/TypeOps.scala | 11 ++--------- compiler/src/dotty/tools/dotc/typer/Namer.scala | 3 ++- tests/pos/i10693.scala | 8 ++++++++ 3 files changed, 12 insertions(+), 10 deletions(-) create mode 100644 tests/pos/i10693.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 587c52688456..1bec455c5495 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -157,15 +157,8 @@ object TypeOps: tp.derivedAlias(simplify(tp.alias, theMap)) case AndType(l, r) if !ctx.mode.is(Mode.Type) => simplify(l, theMap) & simplify(r, theMap) - case tp @ OrType(l, r) - if !ctx.mode.is(Mode.Type) - && (tp.isSoft || l.isBottomType || r.isBottomType) => - // Normalize A | Null and Null | A to A even if the union is hard (i.e. - // explicitly declared), but not if -Yexplicit-nulls is set. The reason is - // that in this case the normal asSeenFrom machinery is not prepared to deal - // with Nulls (which have no base classes). Under -Yexplicit-nulls, we take - // corrective steps, so no widening is wanted. - simplify(l, theMap) | simplify(r, theMap) + case tp @ OrType(l, r) if !ctx.mode.is(Mode.Type) => + TypeComparer.lub(simplify(l, theMap), simplify(r, theMap), isSoft = tp.isSoft) case tp @ CapturingType(parent, refs) => if !ctx.mode.is(Mode.Type) && refs.subCaptures(parent.captureSet, frozen = true).isOK diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 24721f1cd758..c1bde8d4fd3c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1961,7 +1961,8 @@ class Namer { typer: Typer => else // don't strip @uncheckedVariance annot for default getters TypeOps.simplify(tp.widenTermRefExpr, - if defaultTp.exists then TypeOps.SimplifyKeepUnchecked() else null) match + if defaultTp.exists then TypeOps.SimplifyKeepUnchecked() else null) + match case ctp: ConstantType if sym.isInlineVal => ctp case tp => TypeComparer.widenInferred(tp, pt, widenUnions = true) diff --git a/tests/pos/i10693.scala b/tests/pos/i10693.scala new file mode 100644 index 000000000000..122984484658 --- /dev/null +++ b/tests/pos/i10693.scala @@ -0,0 +1,8 @@ +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) From 5bf3227a7adc7d73e3a6fb25fad109987981ecd8 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 26 Mar 2024 18:57:41 +0100 Subject: [PATCH 2/3] Also re-lub unions in widenUnionWithoutNull --- compiler/src/dotty/tools/dotc/core/Types.scala | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 701a6360fd3d..6c47805997a7 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3598,12 +3598,11 @@ object Types extends TypeUtils { override def widenUnionWithoutNull(using Context): Type = if myUnionPeriod != ctx.period then - myUnion = - if isSoft then - TypeComparer.lub(tp1.widenUnionWithoutNull, tp2.widenUnionWithoutNull, canConstrain = true, isSoft = isSoft) match - case union: OrType => union.join - case res => res - else derivedOrType(tp1.widenUnionWithoutNull, tp2.widenUnionWithoutNull, soft = isSoft) + val union = TypeComparer.lub( + tp1.widenUnionWithoutNull, tp2.widenUnionWithoutNull, canConstrain = isSoft, isSoft = isSoft) + myUnion = union match + case union: OrType if isSoft => union.join + case _ => union if !isProvisional then myUnionPeriod = ctx.period myUnion From 5a158924f3b4277064928a0acf0d6b1d1fda3773 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 27 Mar 2024 17:36:33 +0100 Subject: [PATCH 3/3] Add test case supplied by @LucySMartin --- compiler/test-resources/repl/10693 | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 compiler/test-resources/repl/10693 diff --git a/compiler/test-resources/repl/10693 b/compiler/test-resources/repl/10693 new file mode 100644 index 000000000000..ab4d175c1a6f --- /dev/null +++ b/compiler/test-resources/repl/10693 @@ -0,0 +1,16 @@ +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: String | Int +scala> def d3 = test(d1, d0) +def d3: Int | String +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: String | Int \ No newline at end of file