From 9a998d43f1da450d547515192f658cbe9fbce2dd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 14 Jun 2021 18:47:51 +0200 Subject: [PATCH 1/9] Refactor stopAtStatic Introduce a third boundary for stopping at packages --- .../tools/dotc/core/ConstraintHandling.scala | 2 +- .../dotty/tools/dotc/core/TypeComparer.scala | 2 +- .../src/dotty/tools/dotc/core/Types.scala | 30 ++++++++++++------- .../core/unpickleScala2/Scala2Unpickler.scala | 2 +- .../src/dotty/tools/dotc/typer/Checking.scala | 2 +- .../dotty/tools/dotc/typer/Implicits.scala | 2 +- .../src/dotty/tools/dotc/typer/Inliner.scala | 2 +- .../src/dotty/tools/dotc/typer/Namer.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- 9 files changed, 28 insertions(+), 18 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 7907201c718e..509e9c6991e7 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -99,7 +99,7 @@ trait ConstraintHandling { val bound = dropWildcards(rawBound) val oldBounds @ TypeBounds(lo, hi) = constraint.nonParamBounds(param) val equalBounds = (if isUpper then lo else hi) eq bound - if equalBounds && !bound.existsPart(_ eq param, stopAtStatic = true) then + if equalBounds && !bound.existsPart(_ eq param, StopAt.Static) then // The narrowed bounds are equal and not recursive, // so we can remove `param` from the constraint. constraint = constraint.replace(param, bound) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index f183e0467571..8aabed73b8ef 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -1391,7 +1391,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling */ def canCompare(ts: Set[Type]) = ctx.phase.isTyper - || !ts.exists(_.existsPart(_.isInstanceOf[SkolemType], stopAtStatic = true)) + || !ts.exists(_.existsPart(_.isInstanceOf[SkolemType], StopAt.Static)) def verified(result: Boolean): Boolean = if Config.checkAtomsComparisons then diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index e0c1c35e850a..80e0601a68ee 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -439,14 +439,14 @@ object Types { /** Does this type contain wildcard types? */ final def containsWildcardTypes(using Context) = - existsPart(_.isInstanceOf[WildcardType], stopAtStatic = true, forceLazy = false) + existsPart(_.isInstanceOf[WildcardType], StopAt.Static, forceLazy = false) // ----- Higher-order combinators ----------------------------------- /** Returns true if there is a part of this type that satisfies predicate `p`. */ - final def existsPart(p: Type => Boolean, stopAtStatic: Boolean = false, forceLazy: Boolean = true)(using Context): Boolean = - new ExistsAccumulator(p, stopAtStatic, forceLazy).apply(false, this) + final def existsPart(p: Type => Boolean, stopAt: StopAt = StopAt.None, forceLazy: Boolean = true)(using Context): Boolean = + new ExistsAccumulator(p, stopAt, forceLazy).apply(false, this) /** Returns true if all parts of this type satisfy predicate `p`. */ @@ -454,8 +454,8 @@ object Types { !existsPart(!p(_)) /** Performs operation on all parts of this type */ - final def foreachPart(p: Type => Unit, stopAtStatic: Boolean = false)(using Context): Unit = - new ForeachAccumulator(p, stopAtStatic).apply((), this) + final def foreachPart(p: Type => Unit, stopAt: StopAt = StopAt.None)(using Context): Unit = + new ForeachAccumulator(p, stopAt).apply((), this) /** The parts of this type which are type or term refs and which * satisfy predicate `p`. @@ -5199,6 +5199,12 @@ object Types { // ----- TypeMaps -------------------------------------------------------------------- + /** Where a traversal should stop */ + enum StopAt: + case None // traverse everything + case Package // stop at package references + case Static // stop at static references + /** Common base class of TypeMap and TypeAccumulator */ abstract class VariantTraversal: protected[core] var variance: Int = 1 @@ -5211,7 +5217,7 @@ object Types { res } - protected def stopAtStatic: Boolean = true + protected def stopAt: StopAt = StopAt.Static /** Can the prefix of this static reference be omitted if the reference * itself can be omitted? Overridden in TypeOps#avoid. @@ -5220,7 +5226,11 @@ object Types { protected def stopBecauseStaticOrLocal(tp: NamedType)(using Context): Boolean = (tp.prefix eq NoPrefix) - || stopAtStatic && tp.currentSymbol.isStatic && isStaticPrefix(tp.prefix) + || { + val stop = stopAt + stop == StopAt.Static && tp.currentSymbol.isStatic && isStaticPrefix(tp.prefix) + || stop == StopAt.Package && tp.currentSymbol.is(Package) + } end VariantTraversal abstract class TypeMap(implicit protected var mapCtx: Context) @@ -5409,7 +5419,7 @@ object Types { derivedClassInfo(tp, this(tp.prefix)) def andThen(f: Type => Type): TypeMap = new TypeMap { - override def stopAtStatic = thisMap.stopAtStatic + override def stopAt = thisMap.stopAt def apply(tp: Type) = f(thisMap(tp)) } } @@ -5831,12 +5841,12 @@ object Types { class ExistsAccumulator( p: Type => Boolean, - override val stopAtStatic: Boolean, + override val stopAt: StopAt, forceLazy: Boolean)(using Context) extends TypeAccumulator[Boolean]: def apply(x: Boolean, tp: Type): Boolean = x || p(tp) || (forceLazy || !tp.isInstanceOf[LazyRef]) && foldOver(x, tp) - class ForeachAccumulator(p: Type => Unit, override val stopAtStatic: Boolean)(using Context) extends TypeAccumulator[Unit] { + class ForeachAccumulator(p: Type => Unit, override val stopAt: StopAt)(using Context) extends TypeAccumulator[Unit] { def apply(x: Unit, tp: Type): Unit = foldOver(p(tp), tp) } diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index 8684d7316963..1a70fb1e9d2d 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -700,7 +700,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas } // Cannot use standard `existsPart` method because it calls `lookupRefined` // which can cause CyclicReference errors. - val isBoundAccumulator = new ExistsAccumulator(isBound, stopAtStatic = true, forceLazy = true): + val isBoundAccumulator = new ExistsAccumulator(isBound, StopAt.Static, forceLazy = true): override def foldOver(x: Boolean, tp: Type): Boolean = tp match case tp: TypeRef => applyToPrefix(x, tp) case _ => super.foldOver(x, tp) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 6abc4ccfd090..e1415f908cb8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1088,7 +1088,7 @@ trait Checking { } case _ => } - tp.foreachPart(check, stopAtStatic = true) + tp.foreachPart(check, StopAt.Static) if (ok) tp else UnspecifiedErrorType } diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index ed8b347679b5..d3e6c9102a46 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -732,7 +732,7 @@ trait ImplicitRunInfo: case null => record(i"implicitScope") val liftToAnchors = new TypeMap: - override def stopAtStatic = true + override def stopAt = StopAt.Static private val seen = util.HashSet[Type]() def applyToUnderlying(t: TypeProxy) = diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 973df2e5756c..12f656c55c40 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -669,7 +669,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { /** Register type of leaf node */ private def registerLeaf(tree: Tree): Unit = tree match { case _: This | _: Ident | _: TypeTree => - tree.typeOpt.foreachPart(registerType, stopAtStatic = true) + tree.typeOpt.foreachPart(registerType, StopAt.Static) case _ => } diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 0f6f7e46a39a..6a47cf143d62 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1519,7 +1519,7 @@ class Namer { typer: Typer => approxTp.stripPoly match case atp @ defn.ContextFunctionType(_, resType, _) if !defn.isNonRefinedFunction(atp) // in this case `resType` is lying, gives us only the non-dependent upper bound - || resType.existsPart(_.isInstanceOf[WildcardType], stopAtStatic = true, forceLazy = false) => + || resType.existsPart(_.isInstanceOf[WildcardType], StopAt.Static, forceLazy = false) => originalTp case _ => approxTp diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 536a80626380..902f8506c870 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2803,7 +2803,7 @@ class Typer extends Namer // see tests/pos/i7778b.scala val paramTypes = { - val hasWildcard = formals.exists(_.existsPart(_.isInstanceOf[WildcardType], stopAtStatic = true)) + val hasWildcard = formals.exists(_.existsPart(_.isInstanceOf[WildcardType], StopAt.Static)) if hasWildcard then formals.map(_ => untpd.TypeTree()) else formals.map(untpd.TypeTree) } From 279c185ac1f3a0ad092a95195af72f67e01a763d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 14 Jun 2021 21:58:34 +0200 Subject: [PATCH 2/9] Handle opaque types when inlining. The idea is described in part in the comments to PR #12815. 1. If a this-proxy definition contains references to classes that see opaque types, replace those references by other proxies that expose the opaque alias in a refinement type of the original reference type. 2. Make sure this proxies exist even for static moules containing opaque types, so that step 1 can be applied. 3. With steps 1 and 2, we can drop the restriction that inline methods may not be defined where opaque aliases are visible. --- .../src/dotty/tools/dotc/typer/Inliner.scala | 116 ++++++++++++++++-- .../tools/dotc/typer/PrepareInlineable.scala | 2 - tests/neg/i6662.scala | 15 --- tests/pos/i6662.scala | 20 +++ tests/{neg => pos}/i6854.scala | 2 +- tests/{neg => pos}/inline3.scala | 9 +- tests/pos/opaque-inline.scala | 20 +++ tests/pos/opaque-inline1-transparent.scala | 11 ++ tests/pos/opaque-inline1.scala | 11 ++ tests/pos/opaque-inline2-transparent.scala | 28 +++++ tests/pos/opaque-inline2.check | 3 + tests/pos/opaque-inline2.scala | 28 +++++ tests/run/opaque-inline/EmailAddress.scala | 10 ++ tests/run/opaque-inline/EmailAddressOps.scala | 33 +++++ tests/run/opaque-inline/Test.scala | 2 + tests/run/opaque-inline/TestEmail.scala | 7 ++ tests/run/opaque-inline2.scala | 28 +++++ 17 files changed, 312 insertions(+), 33 deletions(-) delete mode 100644 tests/neg/i6662.scala create mode 100644 tests/pos/i6662.scala rename tests/{neg => pos}/i6854.scala (69%) rename tests/{neg => pos}/inline3.scala (58%) create mode 100644 tests/pos/opaque-inline.scala create mode 100644 tests/pos/opaque-inline1-transparent.scala create mode 100644 tests/pos/opaque-inline1.scala create mode 100644 tests/pos/opaque-inline2-transparent.scala create mode 100644 tests/pos/opaque-inline2.check create mode 100644 tests/pos/opaque-inline2.scala create mode 100644 tests/run/opaque-inline/EmailAddress.scala create mode 100644 tests/run/opaque-inline/EmailAddressOps.scala create mode 100644 tests/run/opaque-inline/Test.scala create mode 100644 tests/run/opaque-inline/TestEmail.scala create mode 100644 tests/run/opaque-inline2.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 12f656c55c40..252a1bdbfcc2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -556,7 +556,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { ref(lastSelf).outerSelect(lastLevel - level, selfSym.info) else inlineCallPrefix - val binding = ValDef(selfSym.asTerm, QuoteUtils.changeOwnerOfTree(rhs, selfSym)).withSpan(selfSym.span) + val binding = accountForOpaques( + ValDef(selfSym.asTerm, QuoteUtils.changeOwnerOfTree(rhs, selfSym)).withSpan(selfSym.span)) bindingsBuf += binding inlining.println(i"proxy at $level: $selfSym = ${bindingsBuf.last}") lastSelf = selfSym @@ -564,10 +565,83 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { } } + /** A list of pairs between TermRefs appearing in thisProxy bindings that + * refer to objects with opaque type aliases and local proxy symbols + * that contain refined versions of these TermRefs where the aliases + * are exposed. + */ + private val opaqueProxies = new mutable.ListBuffer[(TermRef, TermRef)] + + /** Map first halfs of opaqueProxies pairs to second halfs, using =:= as equality */ + def mapRef(ref: TermRef): Option[TermRef] = + opaqueProxies + .find((from, to) => from.symbol == ref.symbol && from =:= ref) + .map(_._2) + + /** If `binding` contains TermRefs that refer to objects with opaque + * type aliases, add proxy definitions that expose these aliases + * and substitute such TermRefs with theproxies. Example from pos/opaque-inline1.scala: + * + * object refined: + * opaque type Positive = Int + * inline def Positive(value: Int): Positive = f(value) + * def f(x: Positive): Positive = x + * def run: Unit = { val x = 9; val nine = refined.Positive(x) } + * + * This generates the following proxies: + * + * val $proxy1: refined.type{type Positive = Int} = + * refined.$asInstanceOf$[refined.type{type Positive = Int}] + * val refined$_this: ($proxy1 : refined.type{Positive = Int}) = + * $proxy1 + * + * and every reference to `refined` in the inlined expression is replaced by + * `refined_$this`. + */ + def accountForOpaques(binding: ValDef)(using Context): ValDef = + binding.symbol.info.foreachPart { + case ref: TermRef => + for cls <- ref.widen.classSymbols do + if cls.containsOpaques && mapRef(ref).isEmpty then + def openOpaqueAliases(selfType: Type): List[(Name, Type)] = selfType match + case RefinedType(parent, rname, TypeAlias(alias)) => + val opaq = cls.info.member(rname).symbol + if opaq.isOpaqueAlias then + (rname, alias.stripLazyRef.asSeenFrom(ref, cls)) + :: openOpaqueAliases(parent) + else Nil + case _ => + Nil + val refinements = openOpaqueAliases(cls.givenSelfType) + val refinedType = refinements.foldLeft(ref: Type) ((parent, refinement) => + RefinedType(parent, refinement._1, TypeAlias(refinement._2)) + ) + val refiningSym = newSym(InlineBinderName.fresh(), Synthetic, refinedType).asTerm + val refiningDef = ValDef(refiningSym, tpd.ref(ref).cast(refinedType)).withSpan(binding.span) + inlining.println(i"add opaque alias proxy $refiningDef") + bindingsBuf += refiningDef + opaqueProxies += ((ref, refiningSym.termRef)) + case _ => + } + if opaqueProxies.isEmpty then binding + else + val mapType = new TypeMap: + override def stopAt = StopAt.Package + def apply(t: Type) = mapOver { + t match + case ref: TermRef => mapRef(ref).getOrElse(ref) + case _ => t + } + binding.symbol.info = mapType(binding.symbol.info) + val mapTree = TreeTypeMap(typeMap = mapType) + mapTree.transform(binding).asInstanceOf[ValDef] + .showing(i"transformed this binding exposing opaque aliases: $result", inlining) + end accountForOpaques + private def canElideThis(tpe: ThisType): Boolean = - inlineCallPrefix.tpe == tpe && ctx.owner.isContainedIn(tpe.cls) || - tpe.cls.isContainedIn(inlinedMethod) || - tpe.cls.is(Package) + inlineCallPrefix.tpe == tpe && ctx.owner.isContainedIn(tpe.cls) + || tpe.cls.isContainedIn(inlinedMethod) + || tpe.cls.is(Package) /** Very similar to TreeInfo.isPureExpr, but with the following inliner-only exceptions: * - synthetic case class apply methods, when the case class constructor is empty, are @@ -666,12 +740,25 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { case _ => } + private val registerTypes = new TypeTraverser: + override def stopAt = StopAt.Package + // Only register ThisType prefixes that see opaques. No need to register the others + // since they are static prefixes. + def registerStaticPrefix(t: Type): Unit = t match + case t: ThisType if t.cls.seesOpaques => registerType(t) + case t: NamedType => registerStaticPrefix(t.prefix) + case _ => + override def traverse(t: Type) = t match + case t: NamedType if t.currentSymbol.isStatic => + registerStaticPrefix(t.prefix) + case t => + registerType(t) + traverseChildren(t) + /** Register type of leaf node */ - private def registerLeaf(tree: Tree): Unit = tree match { - case _: This | _: Ident | _: TypeTree => - tree.typeOpt.foreachPart(registerType, StopAt.Static) + private def registerLeaf(tree: Tree): Unit = tree match + case _: This | _: Ident | _: TypeTree => registerTypes.traverse(tree.typeOpt) case _ => - } /** Make `tree` part of inlined expansion. This means its owner has to be changed * from its `originalOwner`, and, if it comes from outside the inlined method @@ -797,6 +884,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { val inliner = new InlinerMap( typeMap = new DeepTypeMap { + override def stopAt = + if opaqueProxies.isEmpty then StopAt.Static else StopAt.Package def apply(t: Type) = t match { case t: ThisType => thisProxy.getOrElse(t.cls, t) case t: TypeRef => paramProxy.getOrElse(t, mapOver(t)) @@ -843,7 +932,16 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { // Apply inliner to `rhsToInline`, split off any implicit bindings from result, and // make them part of `bindingsBuf`. The expansion is then the tree that remains. - val expansion = inliner.transform(rhsToInline) + val expansion0 = inliner.transform(rhsToInline) + val expansion = + if opaqueProxies.nonEmpty && !inlinedMethod.is(Transparent) then + expansion0.cast(call.tpe)(using ctx.withSource(expansion0.source)) + // the cast makes sure that the sealing with the declared type + // is type correct. Without it we might get problems since the + // expression's type is the opaque alias but the call's type is + // the opaque type itself. An example is in pos/opaque-inline1.scala. + else + expansion0 def issueError() = callValueArgss match { case (msgArg :: Nil) :: Nil => diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala b/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala index 5b52fa883403..b919b87af085 100644 --- a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala +++ b/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala @@ -259,8 +259,6 @@ object PrepareInlineable { } private def checkInlineMethod(inlined: Symbol, body: Tree)(using Context): body.type = { - if (inlined.owner.isClass && inlined.owner.seesOpaques) - report.error(em"Implementation restriction: No inline methods allowed where opaque type aliases are in scope", inlined.srcPos) if Inliner.inInlineMethod(using ctx.outer) then report.error(ex"Implementation restriction: nested inline methods are not supported", inlined.srcPos) diff --git a/tests/neg/i6662.scala b/tests/neg/i6662.scala deleted file mode 100644 index ccad0675da71..000000000000 --- a/tests/neg/i6662.scala +++ /dev/null @@ -1,15 +0,0 @@ -opaque type Opt[A >: Null] = A - -extension [A >: Null](x: Opt[A]) inline def nonEmpty: Boolean = x.get != null // error: Implementation restriction -extension [A >: Null](x: Opt[A]) inline def isEmpty: Boolean = x.get == null // error: Implementation restriction -extension [A >: Null](x: Opt[A]) inline def isDefined: Boolean = x.nonEmpty // error: Implementation restriction -extension [A >: Null](x: Opt[A]) inline def get: A = Opt.unOpt(x) // error: Implementation restriction - -object Opt -{ - inline def unOpt[A >: Null](x: Opt[A]): A = x // error: Implementation restriction - inline def apply[A >: Null](x: A): Opt[A] = x // error: Implementation restriction - inline def some[A >: Null](x: A): Opt[A] = x // error: Implementation restriction - inline def none[A >: Null]: Opt[A] = null // error: Implementation restriction - inline def fromOption[A >: Null](x: Option[A]) = x.orNull // error: Implementation restriction -} diff --git a/tests/pos/i6662.scala b/tests/pos/i6662.scala new file mode 100644 index 000000000000..af0c939c7ed9 --- /dev/null +++ b/tests/pos/i6662.scala @@ -0,0 +1,20 @@ +object opt: + opaque type Opt[A >: Null] = A + object Opt: + inline def unOpt[A >: Null](x: Opt[A]): A = x + inline def apply[A >: Null](x: A): Opt[A] = x + inline def some[A >: Null](x: A): Opt[A] = x + inline def none[A >: Null]: Opt[A] = null + inline def fromOption[A >: Null](x: Option[A]) = x.orNull + +import opt.Opt +extension [A >: Null](x: Opt[A]) + inline def nonEmpty : Boolean = x.get != null + inline def isEmpty : Boolean = x.get == null + inline def isDefined: Boolean = x.nonEmpty + inline def get : A = Opt.unOpt(x) + +@main def Test = + val x: Opt[String] = Opt.some("abc") + assert(x.nonEmpty) + val y: String = Opt.unOpt(x) diff --git a/tests/neg/i6854.scala b/tests/pos/i6854.scala similarity index 69% rename from tests/neg/i6854.scala rename to tests/pos/i6854.scala index 5eb0f8bf6028..78ba9c19af7e 100644 --- a/tests/neg/i6854.scala +++ b/tests/pos/i6854.scala @@ -7,6 +7,6 @@ object Lib { opaque type IArray2[+T] = Array[_ <: T] object IArray2 { - inline def apply(x: =>Int): IArray2[Int] = Array(x) // error + inline def apply(x: =>Int): IArray2[Int] = Array(x) } } diff --git a/tests/neg/inline3.scala b/tests/pos/inline3.scala similarity index 58% rename from tests/neg/inline3.scala rename to tests/pos/inline3.scala index 90a8ea9bb788..d702ca4d7d47 100644 --- a/tests/neg/inline3.scala +++ b/tests/pos/inline3.scala @@ -4,15 +4,13 @@ object K0 { opaque type ProductInstances[F[_], T] = ErasedProductInstances[F[T]] - inline def summonAsArray[F[_], T]: Array[Any] = ??? // error: Implementation restriction: No inline methods allowed - - inline def mkProductInstances[F[_], T]: ProductInstances[F, T] = // error: Implementation restriction: No inline methods allowed + inline def summonAsArray[F[_], T]: Array[Any] = ??? + inline def mkProductInstances[F[_], T]: ProductInstances[F, T] = new ErasedProductInstances(summonAsArray[F, T]).asInstanceOf[ProductInstances[F, T]] val x: T = "" - inline def foo(x: T): T = "foo".asInstanceOf[T] // error: Implementation restriction: No inline methods allowed - + inline def foo(x: T): T = "foo".asInstanceOf[T] } final class ErasedProductInstances[FT](is0: => Array[Any]) @@ -21,7 +19,6 @@ trait Monoid[A] case class ISB(i: Int) object Test { - //val K0 = new K0 K0.foo(K0.x) K0.mkProductInstances[Monoid, ISB] diff --git a/tests/pos/opaque-inline.scala b/tests/pos/opaque-inline.scala new file mode 100644 index 000000000000..a0279f02fbfe --- /dev/null +++ b/tests/pos/opaque-inline.scala @@ -0,0 +1,20 @@ + +object refined: + opaque type Positive = Int + + object Positive extends PositiveFactory + + trait PositiveFactory: + inline def apply(value: Int): Positive = value + + def f(x: Positive): Positive = x + inline def fapply(value: Int): Positive = + val vv = (value, value) // error: implementation restriction + f(vv._1) + +@main def run: Unit = + import refined.* + val x = 9 + val nine = Positive.apply(x) + val nine1 = Positive.fapply(x) + diff --git a/tests/pos/opaque-inline1-transparent.scala b/tests/pos/opaque-inline1-transparent.scala new file mode 100644 index 000000000000..9f05bcc34f85 --- /dev/null +++ b/tests/pos/opaque-inline1-transparent.scala @@ -0,0 +1,11 @@ + +object refined: + opaque type Positive = Int + transparent inline def Positive(value: Int): Positive = f(value) + def f(x: Positive): Positive = x + +object test: + def run: Unit = + val x = 9 + val nine = refined.Positive(x) + diff --git a/tests/pos/opaque-inline1.scala b/tests/pos/opaque-inline1.scala new file mode 100644 index 000000000000..6c16d8d4be1b --- /dev/null +++ b/tests/pos/opaque-inline1.scala @@ -0,0 +1,11 @@ + +object refined: + opaque type Positive = Int + inline def Positive(value: Int): Positive = f(value) + def f(x: Positive): Positive = x + +object test: + def run: Unit = + val x = 9 + val nine = refined.Positive(x) + diff --git a/tests/pos/opaque-inline2-transparent.scala b/tests/pos/opaque-inline2-transparent.scala new file mode 100644 index 000000000000..b0474c57b19e --- /dev/null +++ b/tests/pos/opaque-inline2-transparent.scala @@ -0,0 +1,28 @@ + +import compiletime.* + +object refined: + opaque type Positive = Int + + object Positive extends PositiveFactory + + trait PositiveFactory: + transparent inline def apply(inline value: Int): Positive = + inline if value < 0 then error(codeOf(value) + " is not positive.") + else value + + transparent inline def safe(value: Int): Positive | IllegalArgumentException = + if value < 0 then IllegalArgumentException(s"$value is not positive") + else value: Positive + +@main def Test: Unit = + import refined.* + val eight = Positive(8) + // val negative = Positive(-1) // This correctly produces a compile error "-1 is not positive." + // val random = Positive(scala.util.Random.nextInt()) // This correctly produces a compile error about being unable to inline the method call + val random = Positive.safe(scala.util.Random.nextInt()) + val safeNegative = Positive.safe(-1) + val safeFive = Positive.safe(5) + println(eight) + println(random) + println(safeFive) \ No newline at end of file diff --git a/tests/pos/opaque-inline2.check b/tests/pos/opaque-inline2.check new file mode 100644 index 000000000000..1a3888c02635 --- /dev/null +++ b/tests/pos/opaque-inline2.check @@ -0,0 +1,3 @@ +8 +java.lang.IllegalArgumentException: -1502782350 is not positive +5 diff --git a/tests/pos/opaque-inline2.scala b/tests/pos/opaque-inline2.scala new file mode 100644 index 000000000000..fda5f6756827 --- /dev/null +++ b/tests/pos/opaque-inline2.scala @@ -0,0 +1,28 @@ + +import compiletime.* + +object refined: + opaque type Positive = Int + + object Positive extends PositiveFactory + + trait PositiveFactory: + inline def apply(inline value: Int): Positive = + inline if value < 0 then error(codeOf(value) + " is not positive.") + else value + + transparent inline def safe(value: Int): Positive | IllegalArgumentException = + if value < 0 then IllegalArgumentException(s"$value is not positive") + else value: Positive + +@main def Test: Unit = + import refined.* + val eight = Positive(8) + // val negative = Positive(-1) // This correctly produces a compile error "-1 is not positive." + // val random = Positive(scala.util.Random.nextInt()) // This correctly produces a compile error about being unable to inline the method call + val random = Positive.safe(scala.util.Random.nextInt()) + val safeNegative = Positive.safe(-1) + val safeFive = Positive.safe(5) + println(eight) + println(random) + println(safeFive) \ No newline at end of file diff --git a/tests/run/opaque-inline/EmailAddress.scala b/tests/run/opaque-inline/EmailAddress.scala new file mode 100644 index 000000000000..de5036a0ca77 --- /dev/null +++ b/tests/run/opaque-inline/EmailAddress.scala @@ -0,0 +1,10 @@ +import scala.quoted.* + +opaque type EmailAddress = String +object EmailAddress extends EmailAddressOps[EmailAddress]: + + given (using s: ToExpr[String]): ToExpr[EmailAddress] = s + + def parse(s: String): Either[String, EmailAddress] = + if (s contains "@") Right(s) + else Left("No @ symbol") diff --git a/tests/run/opaque-inline/EmailAddressOps.scala b/tests/run/opaque-inline/EmailAddressOps.scala new file mode 100644 index 000000000000..217c36dd6724 --- /dev/null +++ b/tests/run/opaque-inline/EmailAddressOps.scala @@ -0,0 +1,33 @@ +import scala.quoted.* + +trait EmailAddressOps[EmailAddressTransparent <: String]: + + inline def apply(inline s: String): EmailAddress = + ${ EmailAddressOps.applyImpl('s) } + + private val pattern = java.util.regex.Pattern.compile("([^@]*)@([^@]*)") + + extension (value: EmailAddressTransparent) + inline def localPart: String = + val matcher = pattern.matcher(value: String) + matcher.matches + matcher.group(1) + inline def domainPart: String = + val matcher = pattern.matcher(value: String) + matcher.matches + matcher.group(2) + +object EmailAddressOps { + def applyImpl(expr: Expr[String])(using Quotes): Expr[EmailAddress] = + import quotes.reflect.* + expr.asTerm match + case Inlined(_, _, Literal(StringConstant(s))) => + EmailAddress.parse(s) match + case Right(email) => Expr(email) + case Left(err) => + report.error(s"Not a valid email address: $err", expr) + '{???} + case _ => + report.error(s"Not a constant", expr) + '{???} +} \ No newline at end of file diff --git a/tests/run/opaque-inline/Test.scala b/tests/run/opaque-inline/Test.scala new file mode 100644 index 000000000000..c228e361e5a9 --- /dev/null +++ b/tests/run/opaque-inline/Test.scala @@ -0,0 +1,2 @@ +@main def Test = + (new TestEmail).run diff --git a/tests/run/opaque-inline/TestEmail.scala b/tests/run/opaque-inline/TestEmail.scala new file mode 100644 index 000000000000..13121f59e1d6 --- /dev/null +++ b/tests/run/opaque-inline/TestEmail.scala @@ -0,0 +1,7 @@ +class TestEmail { + def getDomain(e: EmailAddress): String = e.domainPart + + def run: Unit = + val em = EmailAddress("a@b.c") + assert(getDomain(em) == "b.c") +} diff --git a/tests/run/opaque-inline2.scala b/tests/run/opaque-inline2.scala new file mode 100644 index 000000000000..fda5f6756827 --- /dev/null +++ b/tests/run/opaque-inline2.scala @@ -0,0 +1,28 @@ + +import compiletime.* + +object refined: + opaque type Positive = Int + + object Positive extends PositiveFactory + + trait PositiveFactory: + inline def apply(inline value: Int): Positive = + inline if value < 0 then error(codeOf(value) + " is not positive.") + else value + + transparent inline def safe(value: Int): Positive | IllegalArgumentException = + if value < 0 then IllegalArgumentException(s"$value is not positive") + else value: Positive + +@main def Test: Unit = + import refined.* + val eight = Positive(8) + // val negative = Positive(-1) // This correctly produces a compile error "-1 is not positive." + // val random = Positive(scala.util.Random.nextInt()) // This correctly produces a compile error about being unable to inline the method call + val random = Positive.safe(scala.util.Random.nextInt()) + val safeNegative = Positive.safe(-1) + val safeFive = Positive.safe(5) + println(eight) + println(random) + println(safeFive) \ No newline at end of file From df412c6f09fdea85ca9d9af4dc69254b6c194ae5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 15 Jun 2021 10:09:49 +0200 Subject: [PATCH 3/9] Better job of stripping module class names The lack of this caused a spurious pickling error in i11914a.scala when proxy generation changed. --- compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala | 5 +++-- tests/neg/exports.check | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index d51a28a2c51f..f357a4d2441d 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -78,9 +78,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } override def nameString(name: Name): String = - if ctx.settings.YdebugNames.value then name.debugString + def strippedName = if printDebug then name else name.stripModuleClassSuffix + if ctx.settings.YdebugNames.value then strippedName.debugString else if name.isTypeName && name.is(WildcardParamName) && !printDebug then "_" - else super.nameString(name) + else super.nameString(strippedName) override protected def simpleNameString(sym: Symbol): String = nameString(if (ctx.property(XprintMode).isEmpty) sym.initial.name else sym.name) diff --git a/tests/neg/exports.check b/tests/neg/exports.check index 8eeea9f5db8d..8ba85f183ce7 100644 --- a/tests/neg/exports.check +++ b/tests/neg/exports.check @@ -11,7 +11,7 @@ 25 | export printUnit.bitmap // error: no eligible member | ^ | non-private given instance bitmap in class Copier refers to private value printUnit - | in its type signature => Copier.this.printUnit.bitmap$ + | in its type signature => Copier.this.printUnit.bitmap -- [E120] Naming Error: tests/neg/exports.scala:23:33 ------------------------------------------------------------------ 23 | export printUnit.{stat => _, _} // error: double definition | ^ From b9e76d60dc26315804d049fee65d77729477295c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 15 Jun 2021 10:14:56 +0200 Subject: [PATCH 4/9] Avoid redundant generation of static this proxies We don't need a thos proxy if the target is a statioc object, unless that object sees opaque aliases. This allows to simplify the registerType logic, and avoided some spurious proxy generations, for instance in run-macros/i5119 --- .../dotty/tools/dotc/core/SymDenotations.scala | 2 +- .../src/dotty/tools/dotc/typer/Inliner.scala | 16 ++++------------ 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index ba094c587038..b24a4a7bae2c 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -2546,7 +2546,7 @@ object SymDenotations { } private[SymDenotations] def stillValidInOwner(denot: SymDenotation)(using Context): Boolean = try - val owner = denot.owner.denot + val owner = denot.maybeOwner.denot stillValid(owner) && ( !owner.isClass diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 252a1bdbfcc2..3ab4feaad796 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -642,6 +642,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { inlineCallPrefix.tpe == tpe && ctx.owner.isContainedIn(tpe.cls) || tpe.cls.isContainedIn(inlinedMethod) || tpe.cls.is(Package) + || tpe.cls.isStaticOwner && !(tpe.cls.seesOpaques && ctx.owner.isContainedIn(tpe.cls)) /** Very similar to TreeInfo.isPureExpr, but with the following inliner-only exceptions: * - synthetic case class apply methods, when the case class constructor is empty, are @@ -742,18 +743,9 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { private val registerTypes = new TypeTraverser: override def stopAt = StopAt.Package - // Only register ThisType prefixes that see opaques. No need to register the others - // since they are static prefixes. - def registerStaticPrefix(t: Type): Unit = t match - case t: ThisType if t.cls.seesOpaques => registerType(t) - case t: NamedType => registerStaticPrefix(t.prefix) - case _ => - override def traverse(t: Type) = t match - case t: NamedType if t.currentSymbol.isStatic => - registerStaticPrefix(t.prefix) - case t => - registerType(t) - traverseChildren(t) + override def traverse(t: Type) = + registerType(t) + traverseChildren(t) /** Register type of leaf node */ private def registerLeaf(tree: Tree): Unit = tree match From 363c8645240a38d205679d883f76f01435e1e5bb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 15 Jun 2021 10:52:36 +0200 Subject: [PATCH 5/9] Fix completion test --- .../tools/languageserver/CompletionTest.scala | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/language-server/test/dotty/tools/languageserver/CompletionTest.scala b/language-server/test/dotty/tools/languageserver/CompletionTest.scala index 6ac894800616..615e8f3b4049 100644 --- a/language-server/test/dotty/tools/languageserver/CompletionTest.scala +++ b/language-server/test/dotty/tools/languageserver/CompletionTest.scala @@ -42,7 +42,7 @@ class CompletionTest { @Test def completionFromSyntheticPackageObject: Unit = { code"class Foo { val foo: IArr${m1} }".withSource - .completion(m1, Set(("IArray", Module, "IArray$"), + .completion(m1, Set(("IArray", Module, "IArray"), ("IArray", Field, "scala.IArray"))) } @@ -50,7 +50,7 @@ class CompletionTest { code"class Foo { val foo: Runn${m1} }".withSource .completion(m1, Set( ("Runnable", Class, "java.lang.Runnable"), - ("Runnable", Module, "Runnable$") + ("Runnable", Module, "Runnable") )) } @@ -121,7 +121,7 @@ class CompletionTest { withSources( code"""object O { object MyObject }""", code"""import O.My${m1}""" - ).completion(m1, Set(("MyObject", Module, "O.MyObject$"))) + ).completion(m1, Set(("MyObject", Module, "O.MyObject"))) } @Test def importCompleteWithClassAndCompanion: Unit = { @@ -132,7 +132,7 @@ class CompletionTest { code"""package pgk1 import pkg0.F${m1}""" ).completion(m1, Set(("Foo", Class, "pkg0.Foo"), - ("Foo", Module, "pkg0.Foo$"))) + ("Foo", Module, "pkg0.Foo"))) } @Test def importCompleteIncludePackage: Unit = { @@ -157,7 +157,7 @@ class CompletionTest { ).completion(m1, Set(("myVal", Field, "Int"), ("myDef", Method, "=> Int"), ("myVar", Variable, "Int"), - ("myObject", Module, "MyObject.myObject$"), + ("myObject", Module, "MyObject.myObject"), ("myClass", Class, "MyObject.myClass"), ("myTrait", Class, "MyObject.myTrait"))) } @@ -165,7 +165,7 @@ class CompletionTest { @Test def importJavaClass: Unit = { code"""import java.io.FileDesc${m1}""".withSource .completion(m1, Set(("FileDescriptor", Class, "java.io.FileDescriptor"), - ("FileDescriptor", Module, "java.io.FileDescriptor$"))) + ("FileDescriptor", Module, "java.io.FileDescriptor"))) } @Test def importJavaStaticMethod: Unit = { @@ -190,13 +190,13 @@ class CompletionTest { code"""object O { val out = java.io.FileDesc${m1} }""".withSource - .completion(m1, Set(("FileDescriptor", Module, "java.io.FileDescriptor$"))) + .completion(m1, Set(("FileDescriptor", Module, "java.io.FileDescriptor"))) } @Test def importRename: Unit = { code"""import java.io.{FileDesc${m1} => Foo}""".withSource .completion(m1, Set(("FileDescriptor", Class, "java.io.FileDescriptor"), - ("FileDescriptor", Module, "java.io.FileDescriptor$"))) + ("FileDescriptor", Module, "java.io.FileDescriptor"))) } @Test def importGivenByType: Unit = { @@ -257,14 +257,14 @@ class CompletionTest { | object bat | val bizz: ba${m1} |}""".withSource - .completion(m1, Set(("bar", Field, "Bar"), ("bat", Module, "Foo.bat$"))) + .completion(m1, Set(("bar", Field, "Bar"), ("bat", Module, "Foo.bat"))) } @Test def completionOnRenamedImport: Unit = { code"""import java.io.{FileDescriptor => AwesomeStuff} trait Foo { val x: Awesom$m1 }""".withSource .completion(m1, Set(("AwesomeStuff", Class, "java.io.FileDescriptor"), - ("AwesomeStuff", Module, "java.io.FileDescriptor$"))) + ("AwesomeStuff", Module, "java.io.FileDescriptor"))) } @Test def completionOnRenamedImport2: Unit = { @@ -274,7 +274,7 @@ class CompletionTest { val x: MyImp$m1 }""".withSource .completion(m1, Set(("MyImportedSymbol", Class, "java.io.FileDescriptor"), - ("MyImportedSymbol", Module, "java.io.FileDescriptor$"))) + ("MyImportedSymbol", Module, "java.io.FileDescriptor"))) } @Test def completionRenamedAndOriginalNames: Unit = { @@ -284,9 +284,9 @@ class CompletionTest { | val x: Hash$m1 |}""".withSource .completion(m1, Set(("HashMap", Class, "java.util.HashMap"), - ("HashMap", Module, "java.util.HashMap$"), + ("HashMap", Module, "java.util.HashMap"), ("HashMap2", Class, "java.util.HashMap"), - ("HashMap2", Module, "java.util.HashMap$"))) + ("HashMap2", Module, "java.util.HashMap"))) } @Test def completionRenamedThrice: Unit = { @@ -297,11 +297,11 @@ class CompletionTest { | val x: MyHash$m1 |}""".withSource .completion(m1, Set(("MyHashMap", Class, "java.util.HashMap"), - ("MyHashMap", Module, "java.util.HashMap$"), + ("MyHashMap", Module, "java.util.HashMap"), ("MyHashMap2", Class, "java.util.HashMap"), - ("MyHashMap2", Module, "java.util.HashMap$"), + ("MyHashMap2", Module, "java.util.HashMap"), ("MyHashMap3", Class, "java.util.HashMap"), - ("MyHashMap3", Module, "java.util.HashMap$"))) + ("MyHashMap3", Module, "java.util.HashMap"))) } @Test def completeFromWildcardImports: Unit = { @@ -372,7 +372,7 @@ class CompletionTest { code"""object Test { | def x = Tes$m1 |}""".withSource - .completion(m1, Set(("Test", Module, "Test$"))) + .completion(m1, Set(("Test", Module, "Test"))) } @Test def completeBothDefinitionsForEqualNestingLevels: Unit = { @@ -524,9 +524,9 @@ class CompletionTest { | val ZZZZ = YY$m1 | type ZZZZ = YY$m2 |}""".withSource - .completion(m1, Set(("YYYY", Field, "Int$"))) + .completion(m1, Set(("YYYY", Field, "Int"))) .completion(m2, Set(("YYYY", Field, "XXXX.YYYY"), - ("YYYY", Field, "Int$"))) + ("YYYY", Field, "Int"))) } @Test def completeRespectingAccessModifiers: Unit = { From d7e90d5344f031f19b241de750c842b576e7c219 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 16 Jun 2021 13:09:44 +0200 Subject: [PATCH 6/9] Update check file --- tests/neg-scalajs/js-type-bad-parents.check | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/neg-scalajs/js-type-bad-parents.check b/tests/neg-scalajs/js-type-bad-parents.check index 0375f2fd30c0..ae5e23566f8e 100644 --- a/tests/neg-scalajs/js-type-bad-parents.check +++ b/tests/neg-scalajs/js-type-bad-parents.check @@ -45,7 +45,7 @@ -- Error: tests/neg-scalajs/js-type-bad-parents.scala:36:7 ------------------------------------------------------------- 36 |object C3 extends ScalaClass with js.Any // error |^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - |C3$ extends ScalaClass which does not extend js.Any. + |C3 extends ScalaClass which does not extend js.Any. -- Error: tests/neg-scalajs/js-type-bad-parents.scala:39:6 ------------------------------------------------------------- 38 |@js.native 39 |trait C4 extends ScalaClass with js.Any // error @@ -60,7 +60,7 @@ 42 |@js.native @JSGlobal 43 |object C6 extends ScalaClass with js.Any // error |^ - |C6$ extends ScalaClass which does not extend js.Any. + |C6 extends ScalaClass which does not extend js.Any. -- Error: tests/neg-scalajs/js-type-bad-parents.scala:45:6 ------------------------------------------------------------- 45 |trait C7 extends js.Object with ScalaTrait // error |^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -72,7 +72,7 @@ -- Error: tests/neg-scalajs/js-type-bad-parents.scala:47:7 ------------------------------------------------------------- 47 |object C9 extends js.Object with ScalaTrait // error |^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - |C9$ extends ScalaTrait which does not extend js.Any. + |C9 extends ScalaTrait which does not extend js.Any. -- Error: tests/neg-scalajs/js-type-bad-parents.scala:50:6 ------------------------------------------------------------- 49 |@js.native 50 |trait C10 extends js.Object with ScalaTrait // error @@ -87,4 +87,4 @@ 53 |@js.native @JSGlobal 54 |object C12 extends js.Object with ScalaTrait // error |^ - |C12$ extends ScalaTrait which does not extend js.Any. + |C12 extends ScalaTrait which does not extend js.Any. From 24140338e836286bd891453d564ad3026fc57e14 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 16 Jun 2021 13:10:47 +0200 Subject: [PATCH 7/9] Fix canElideThis condition --- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 3ab4feaad796..535b281f947c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -642,7 +642,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { inlineCallPrefix.tpe == tpe && ctx.owner.isContainedIn(tpe.cls) || tpe.cls.isContainedIn(inlinedMethod) || tpe.cls.is(Package) - || tpe.cls.isStaticOwner && !(tpe.cls.seesOpaques && ctx.owner.isContainedIn(tpe.cls)) + || tpe.cls.isStaticOwner && !(tpe.cls.seesOpaques && inlinedMethod.isContainedIn(tpe.cls)) /** Very similar to TreeInfo.isPureExpr, but with the following inliner-only exceptions: * - synthetic case class apply methods, when the case class constructor is empty, are @@ -743,7 +743,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { private val registerTypes = new TypeTraverser: override def stopAt = StopAt.Package - override def traverse(t: Type) = + override def traverse(t: Type) = registerType(t) traverseChildren(t) From 3f07fb9b39ff106fa5ce57ce65c16e8dcd81d9c7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 16 Jun 2021 13:17:25 +0200 Subject: [PATCH 8/9] Streamline a collection operation --- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 535b281f947c..e2b7039eab79 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -574,9 +574,9 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { /** Map first halfs of opaqueProxies pairs to second halfs, using =:= as equality */ def mapRef(ref: TermRef): Option[TermRef] = - opaqueProxies - .find((from, to) => from.symbol == ref.symbol && from =:= ref) - .map(_._2) + opaqueProxies.collectFirst { + case (from, to) if from.symbol == ref.symbol && from =:= ref => to + } /** If `binding` contains TermRefs that refer to objects with opaque * type aliases, add proxy definitions that expose these aliases From d4e8f5d666ae5abed2fd85d8288c7e8173576a0e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 17 Jun 2021 15:51:30 +0200 Subject: [PATCH 9/9] Also use casts for transparent inlines --- .../src/dotty/tools/dotc/typer/Inliner.scala | 23 ++++++++++--------- tests/pos/opaque-inline1.scala | 2 ++ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index e2b7039eab79..aee1c5040075 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -924,16 +924,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { // Apply inliner to `rhsToInline`, split off any implicit bindings from result, and // make them part of `bindingsBuf`. The expansion is then the tree that remains. - val expansion0 = inliner.transform(rhsToInline) - val expansion = - if opaqueProxies.nonEmpty && !inlinedMethod.is(Transparent) then - expansion0.cast(call.tpe)(using ctx.withSource(expansion0.source)) - // the cast makes sure that the sealing with the declared type - // is type correct. Without it we might get problems since the - // expression's type is the opaque alias but the call's type is - // the opaque type itself. An example is in pos/opaque-inline1.scala. - else - expansion0 + val expansion = inliner.transform(rhsToInline) def issueError() = callValueArgss match { case (msgArg :: Nil) :: Nil => @@ -1005,7 +996,17 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { // Take care that only argument bindings go into `bindings`, since positions are // different for bindings from arguments and bindings from body. - tpd.Inlined(call, finalBindings, finalExpansion) + val res = tpd.Inlined(call, finalBindings, finalExpansion) + if opaqueProxies.isEmpty then res + else + val target = + if inlinedMethod.is(Transparent) then call.tpe & res.tpe + else call.tpe + res.ensureConforms(target) + // Make sure that the sealing with the declared type + // is type correct. Without it we might get problems since the + // expression's type is the opaque alias but the call's type is + // the opaque type itself. An example is in pos/opaque-inline1.scala. } } diff --git a/tests/pos/opaque-inline1.scala b/tests/pos/opaque-inline1.scala index 6c16d8d4be1b..b30eeafdbe77 100644 --- a/tests/pos/opaque-inline1.scala +++ b/tests/pos/opaque-inline1.scala @@ -2,10 +2,12 @@ object refined: opaque type Positive = Int inline def Positive(value: Int): Positive = f(value) + transparent inline def TPositive(value: Int): Positive = f(value) def f(x: Positive): Positive = x object test: def run: Unit = val x = 9 val nine = refined.Positive(x) + val tnine: refined.Positive = refined.TPositive(x)