From 5ce9a9977a4a3684799e35435de60946f428f6a9 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 1 Nov 2021 14:17:09 +0100 Subject: [PATCH 1/2] Detect opaque aliases in inline val types `inline val`s cannot contain opaque aliases as these cannot be inlined through their type due to their opaqueness. We can support `inline def` with opaque types. Fixes #13851 Fixes #13852 --- .../src/dotty/tools/dotc/core/Types.scala | 29 ++++++++++++------- .../tools/dotc/transform/InlineVals.scala | 6 ++-- tests/neg/i13851.scala | 11 +++++++ tests/neg/i13851b.scala | 10 +++++++ tests/neg/i13852.scala | 6 ++++ 5 files changed, 49 insertions(+), 13 deletions(-) create mode 100644 tests/neg/i13851.scala create mode 100644 tests/neg/i13851b.scala create mode 100644 tests/neg/i13852.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index ae7ec91e7108..b384cbdcb084 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1363,25 +1363,26 @@ object Types { case Atoms.Unknown => Atoms.Unknown case _ => Atoms.Unknown - private def dealias1(keep: AnnotatedType => Context ?=> Boolean)(using Context): Type = this match { + private def dealias1(keep: AnnotatedType => Context ?=> Boolean, keepOpaques: Boolean)(using Context): Type = this match { case tp: TypeRef => if (tp.symbol.isClass) tp else tp.info match { - case TypeAlias(alias) => alias.dealias1(keep) + case TypeAlias(alias) if !(keepOpaques && tp.symbol.is(Opaque)) => + alias.dealias1(keep, keepOpaques) case _ => tp } case app @ AppliedType(tycon, _) => - val tycon1 = tycon.dealias1(keep) - if (tycon1 ne tycon) app.superType.dealias1(keep) + val tycon1 = tycon.dealias1(keep, keepOpaques) + if (tycon1 ne tycon) app.superType.dealias1(keep, keepOpaques) else this case tp: TypeVar => val tp1 = tp.instanceOpt - if (tp1.exists) tp1.dealias1(keep) else tp + if (tp1.exists) tp1.dealias1(keep, keepOpaques) else tp case tp: AnnotatedType => - val tp1 = tp.parent.dealias1(keep) + val tp1 = tp.parent.dealias1(keep, keepOpaques) if keep(tp) then tp.derivedAnnotatedType(tp1, tp.annot) else tp1 case tp: LazyRef => - tp.ref.dealias1(keep) + tp.ref.dealias1(keep, keepOpaques) case _ => this } @@ -1389,16 +1390,22 @@ object Types { * TypeVars until type is no longer alias type, annotated type, LazyRef, * or instantiated type variable. */ - final def dealias(using Context): Type = dealias1(keepNever) + final def dealias(using Context): Type = dealias1(keepNever, keepOpaques = false) /** Follow aliases and dereferences LazyRefs and instantiated TypeVars until type * is no longer alias type, LazyRef, or instantiated type variable. * Goes through annotated types and rewraps annotations on the result. */ - final def dealiasKeepAnnots(using Context): Type = dealias1(keepAlways) + final def dealiasKeepAnnots(using Context): Type = dealias1(keepAlways, keepOpaques = false) /** Like `dealiasKeepAnnots`, but keeps only refining annotations */ - final def dealiasKeepRefiningAnnots(using Context): Type = dealias1(keepIfRefining) + final def dealiasKeepRefiningAnnots(using Context): Type = dealias1(keepIfRefining, keepOpaques = false) + + /** Follow non-opaque aliases and dereferences LazyRefs, annotated types and instantiated + * TypeVars until type is no longer alias type, annotated type, LazyRef, + * or instantiated type variable. + */ + final def dealiasKeepOpaques(using Context): Type = dealias1(keepNever, keepOpaques = true) /** Approximate this type with a type that does not contain skolem types. */ final def deskolemized(using Context): Type = @@ -1426,7 +1433,7 @@ object Types { def tryNormalize(using Context): Type = NoType private def widenDealias1(keep: AnnotatedType => Context ?=> Boolean)(using Context): Type = { - val res = this.widen.dealias1(keep) + val res = this.widen.dealias1(keep, keepOpaques = false) if (res eq this) res else res.widenDealias1(keep) } diff --git a/compiler/src/dotty/tools/dotc/transform/InlineVals.scala b/compiler/src/dotty/tools/dotc/transform/InlineVals.scala index 0e3f4139c27b..f3de86aab58a 100644 --- a/compiler/src/dotty/tools/dotc/transform/InlineVals.scala +++ b/compiler/src/dotty/tools/dotc/transform/InlineVals.scala @@ -33,13 +33,15 @@ class InlineVals extends MiniPhase: then val rhs = tree.rhs val tpt = tree.tpt - tpt.tpe.widenTermRefExpr.dealias.normalized match + tpt.tpe.widenTermRefExpr.dealiasKeepOpaques.normalized match case tp: ConstantType => if !isPureExpr(rhs) then val details = if enclosingInlineds.isEmpty then "" else em"but was: $rhs" report.error(s"inline value must be pure$details", rhs.srcPos) case tp => - if tp.derivesFrom(defn.UnitClass) then + if tp.typeSymbol.is(Opaque) then + report.error(em"`inline val` of type opaque types is not supported.\n\nTo inline consider using `inline def`", rhs) + else if tp.derivesFrom(defn.UnitClass) then report.error(em"`inline val` of type `Unit` is not supported.\n\nTo inline a `Unit` consider using `inline def`", rhs) else if tp.derivesFrom(defn.StringClass) || defn.ScalaValueClasses().exists(tp.derivesFrom(_)) then val pos = if tpt.span.isZeroExtent then rhs.srcPos else tpt.srcPos diff --git a/tests/neg/i13851.scala b/tests/neg/i13851.scala new file mode 100644 index 000000000000..fb7066a60b3f --- /dev/null +++ b/tests/neg/i13851.scala @@ -0,0 +1,11 @@ +opaque type One = 1 +inline val One: One = 1 // error + +opaque type Max = Int.MaxValue.type +inline val Max: Max = Int.MaxValue // error + +inline val MaxValue: Int.MaxValue.type = Int.MaxValue + +opaque type Two = 2 +type Bis = Two +inline val Two: Bis = 2 // error \ No newline at end of file diff --git a/tests/neg/i13851b.scala b/tests/neg/i13851b.scala new file mode 100644 index 000000000000..624735de0a49 --- /dev/null +++ b/tests/neg/i13851b.scala @@ -0,0 +1,10 @@ +object Num { + opaque type One = 1 + inline val One: One = 1 // error + + opaque type Two = 2 + inline def Two: Two = 2 +} + +def test1 = Num.One +def test2 = Num.Two diff --git a/tests/neg/i13852.scala b/tests/neg/i13852.scala new file mode 100644 index 000000000000..a0c5e726e1a8 --- /dev/null +++ b/tests/neg/i13852.scala @@ -0,0 +1,6 @@ +inline val `1`: 1 = 1 +def get1: 1 = `1` + +opaque type One = 1 +inline val One: One = 1 // error +def getOne: One = One From 9b549ab337c9cf5556d22554c51070df6ab00b5a Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 2 Nov 2021 10:31:37 +0100 Subject: [PATCH 2/2] Update compiler/src/dotty/tools/dotc/transform/InlineVals.scala Co-authored-by: Guillaume Martres --- compiler/src/dotty/tools/dotc/transform/InlineVals.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/InlineVals.scala b/compiler/src/dotty/tools/dotc/transform/InlineVals.scala index f3de86aab58a..30ef0ed40375 100644 --- a/compiler/src/dotty/tools/dotc/transform/InlineVals.scala +++ b/compiler/src/dotty/tools/dotc/transform/InlineVals.scala @@ -40,7 +40,7 @@ class InlineVals extends MiniPhase: report.error(s"inline value must be pure$details", rhs.srcPos) case tp => if tp.typeSymbol.is(Opaque) then - report.error(em"`inline val` of type opaque types is not supported.\n\nTo inline consider using `inline def`", rhs) + report.error(em"The type of an `inline val` cannot be an opaque type.\n\nTo inline, consider using `inline def` instead", rhs) else if tp.derivesFrom(defn.UnitClass) then report.error(em"`inline val` of type `Unit` is not supported.\n\nTo inline a `Unit` consider using `inline def`", rhs) else if tp.derivesFrom(defn.StringClass) || defn.ScalaValueClasses().exists(tp.derivesFrom(_)) then