diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index abe17e21c2ef..589048b9642c 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -46,6 +46,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling state = c.typerState monitored = false GADTused = false + opaquesUsed = false recCount = 0 needsGc = false if Config.checkTypeComparerReset then checkReset() @@ -61,6 +62,9 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling /** Indicates whether the subtype check used GADT bounds */ private var GADTused: Boolean = false + /** Indicates whether the subtype check used opaque types */ + private var opaquesUsed: Boolean = false + private var myInstance: TypeComparer = this def currentInstance: TypeComparer = myInstance @@ -142,8 +146,10 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling def testSubType(tp1: Type, tp2: Type): CompareResult = GADTused = false + opaquesUsed = false if !topLevelSubType(tp1, tp2) then CompareResult.Fail else if GADTused then CompareResult.OKwithGADTUsed + else if opaquesUsed then CompareResult.OKwithOpaquesUsed // we cast on GADTused, so handles if both are used else CompareResult.OK /** The current approximation state. See `ApproxState`. */ @@ -1483,12 +1489,12 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling def tryLiftedToThis1: Boolean = { val tp1a = liftToThis(tp1) - (tp1a ne tp1) && recur(tp1a, tp2) + (tp1a ne tp1) && recur(tp1a, tp2) && { opaquesUsed = true; true } } def tryLiftedToThis2: Boolean = { val tp2a = liftToThis(tp2) - (tp2a ne tp2) && recur(tp1, tp2a) + (tp2a ne tp2) && recur(tp1, tp2a) && { opaquesUsed = true; true } } // begin recur @@ -2935,7 +2941,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling object TypeComparer { enum CompareResult: - case OK, Fail, OKwithGADTUsed + case OK, Fail, OKwithGADTUsed, OKwithOpaquesUsed /** Class for unification variables used in `natValue`. */ private class AnyConstantType extends UncachedGroundType with ValueType { diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 44bb77bf040c..de611db6c14e 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -1,4 +1,5 @@ -package dotty.tools.dotc +package dotty.tools +package dotc package transform import core.Names.Name diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 51787cb5004a..a42abd2354c6 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4063,6 +4063,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer res } => insertGadtCast(tree, wtp, pt) + case CompareResult.OKwithOpaquesUsed if !tree.tpe.frozen_<:<(pt)(using ctx.withOwner(defn.RootClass)) => + // guard to avoid extra Typed trees, eg. from testSubType(O.T, O.T) which returns OKwithOpaquesUsed + Typed(tree, TypeTree(pt)) case _ => //typr.println(i"OK ${tree.tpe}\n${TypeComparer.explained(_.isSubType(tree.tpe, pt))}") // uncomment for unexpected successes tree diff --git a/tests/pos/i17948.all.scala b/tests/pos/i17948.all.scala new file mode 100644 index 000000000000..2c184872ff38 --- /dev/null +++ b/tests/pos/i17948.all.scala @@ -0,0 +1,44 @@ +object O: + opaque type T = Int + + inline def get0: Int = Do.get // no proxy needed + inline def get1: Int = Do.get: O.T // no proxy needed + inline def get2: Int = Do.get: T // proxied + + inline def set0: Unit = Do.set(0) // was: broken + inline def set1: Unit = Do.set(1: O.T) // no proxy needed + inline def set2: Unit = Do.set(2: T) // proxied + + inline def mod0: Int = Do.mod(0) // was: broken + inline def mod1: Int = Do.mod(1): O.T // was: broken + inline def mod2: Int = Do.mod(2): T // was: broken + inline def mod3: Int = Do.mod(3: O.T) // no proxy needed + inline def mod4: Int = Do.mod(4: O.T): O.T // no proxy needed + inline def mod5: Int = Do.mod(5: O.T): T // proxied + inline def mod6: Int = Do.mod(6: T) // proxied + inline def mod7: Int = Do.mod(7: T): O.T // proxied + inline def mod8: Int = Do.mod(8: T): T // proxied + +class Test: + def testGet0: Int = O.get0 + def testGet1: Int = O.get1 + def testGet2: Int = O.get2 + + def testSet0: Unit = O.set0 + def testSet1: Unit = O.set1 + def testSet2: Unit = O.set2 + + def testMod0: Int = O.mod0 + def testMod1: Int = O.mod1 + def testMod2: Int = O.mod2 + def testMod3: Int = O.mod3 + def testMod4: Int = O.mod4 + def testMod5: Int = O.mod5 + def testMod6: Int = O.mod6 + def testMod7: Int = O.mod7 + def testMod8: Int = O.mod8 + +object Do: + def get: O.T = ??? + def set(x: O.T): Unit = () + def mod(x: O.T): O.T = x diff --git a/tests/pos/i17948.scala b/tests/pos/i17948.scala new file mode 100644 index 000000000000..3fb927e139fd --- /dev/null +++ b/tests/pos/i17948.scala @@ -0,0 +1,12 @@ +object O: + opaque type T = Int + inline def x: Int = P.id(2) + +object P: + def id(x: O.T): O.T = x + +object Test { + def main(args: Array[String]): Unit = println(foo()) + + def foo(): Int = O.x +}