From c3692eeb3b9d687cd9cd09ed44bc10fce0fb86ae Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 29 Oct 2023 17:16:52 +0100 Subject: [PATCH 1/4] Detect case where two alternatives are the same after widening ExprTypes In implicit or extension method search we might get two alternatives that are different but that point to the same singleton type after widening ExprTypes. In that case we can arbitrarily pick one of them instead of declaring an ambiguity. Fixes #18768 --- .../dotty/tools/dotc/typer/Applications.scala | 21 ++++++++++++------- tests/pos/i18768.scala | 14 +++++++++++++ 2 files changed, 28 insertions(+), 7 deletions(-) create mode 100644 tests/pos/i18768.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index e6ff916cc672..fe068fabac00 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1780,14 +1780,21 @@ trait Applications extends Compatibility { def winsType2 = isAsSpecific(alt2, tp2, alt1, tp1) overload.println(i"compare($alt1, $alt2)? $tp1 $tp2 $ownerScore $winsType1 $winsType2") - if (ownerScore == 1) - if (winsType1 || !winsType2) 1 else 0 - else if (ownerScore == -1) - if (winsType2 || !winsType1) -1 else 0 - else if (winsType1) - if (winsType2) 0 else 1 + if winsType1 && winsType2 + && alt1.widenExpr =:= alt2.widenExpr + && alt1.widenExpr.isStable + then + // alternatives are the same after following ExprTypes, pick one of them + // (prefer the one that is not a method, but that's arbitrary). + if alt1.widenExpr =:= alt2 then -1 else 1 + else if ownerScore == 1 then + if winsType1 || !winsType2 then 1 else 0 + else if ownerScore == -1 then + if winsType2 || !winsType1 then -1 else 0 + else if winsType1 then + if winsType2 then 0 else 1 else - if (winsType2) -1 else 0 + if winsType2 then -1 else 0 } if alt1.symbol.is(ConstructorProxy) && !alt2.symbol.is(ConstructorProxy) then -1 diff --git a/tests/pos/i18768.scala b/tests/pos/i18768.scala new file mode 100644 index 000000000000..82f9bd43c4b5 --- /dev/null +++ b/tests/pos/i18768.scala @@ -0,0 +1,14 @@ +object Module: + object Exportee: + + opaque type Id = Long + + def apply(): Id = ??? + + extension (e: Id) + def updated: Id = ??? + + +object Client: + export Module.* + val x = Exportee().updated From c2314e9fd7ef84f13e9000fe4ac1d4ad266967fe Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 29 Oct 2023 18:11:47 +0100 Subject: [PATCH 2/4] Fix comparison to be in frozen constraint --- compiler/src/dotty/tools/dotc/typer/Applications.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index fe068fabac00..c1680a0a902f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1781,8 +1781,7 @@ trait Applications extends Compatibility { overload.println(i"compare($alt1, $alt2)? $tp1 $tp2 $ownerScore $winsType1 $winsType2") if winsType1 && winsType2 - && alt1.widenExpr =:= alt2.widenExpr - && alt1.widenExpr.isStable + && (alt1.widenExpr frozen_=:= alt2.widenExpr) && alt1.widenExpr.isStable then // alternatives are the same after following ExprTypes, pick one of them // (prefer the one that is not a method, but that's arbitrary). From 86f339a51693948506144a0de49d9306a32e37b2 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 30 Oct 2023 09:00:28 +0100 Subject: [PATCH 3/4] Add original test --- tests/pos/i18768.scala | 48 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/tests/pos/i18768.scala b/tests/pos/i18768.scala index 82f9bd43c4b5..67a5bd127200 100644 --- a/tests/pos/i18768.scala +++ b/tests/pos/i18768.scala @@ -1,14 +1,44 @@ -object Module: - object Exportee: +package minimized: + object Module: + object Exportee: - opaque type Id = Long + opaque type Id = Long - def apply(): Id = ??? + def apply(): Id = ??? - extension (e: Id) - def updated: Id = ??? + extension (e: Id) + def updated: Id = ??? -object Client: - export Module.* - val x = Exportee().updated + object Client: + export Module.* + val x = Exportee().updated + +package original: + object Module: + trait EntityDef: + type Id + type Record + type Entity = (Id, Record) + + extension (e: Entity) + def updated: Entity = e + + case class Exportee() + object Exportee extends EntityDef: + opaque type Id = Long + type Record = Exportee + + def apply(id: Long): Entity = (id, Exportee()) + + object Client: + export Module.* + val x = Exportee(1L).updated + + + object ClientWorkingWithManualExport: + export Module.{Exportee as _, *} + type Exportee = Module.Exportee + val Exportee = Module.Exportee + + val x = Exportee(1L).updated From c45694743e22915d2e9dac5ac32c7f8ad41a28a7 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 30 Oct 2023 15:15:44 +0100 Subject: [PATCH 4/4] Update compiler/src/dotty/tools/dotc/typer/Applications.scala --- compiler/src/dotty/tools/dotc/typer/Applications.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index c1680a0a902f..95df3101641a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1781,7 +1781,7 @@ trait Applications extends Compatibility { overload.println(i"compare($alt1, $alt2)? $tp1 $tp2 $ownerScore $winsType1 $winsType2") if winsType1 && winsType2 - && (alt1.widenExpr frozen_=:= alt2.widenExpr) && alt1.widenExpr.isStable + && alt1.widenExpr.isStable && (alt1.widenExpr frozen_=:= alt2.widenExpr) then // alternatives are the same after following ExprTypes, pick one of them // (prefer the one that is not a method, but that's arbitrary).