@@ -556,18 +556,93 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
556556 ref(lastSelf).outerSelect(lastLevel - level, selfSym.info)
557557 else
558558 inlineCallPrefix
559- val binding = ValDef (selfSym.asTerm, QuoteUtils .changeOwnerOfTree(rhs, selfSym)).withSpan(selfSym.span)
559+ val binding = accountForOpaques(
560+ ValDef (selfSym.asTerm, QuoteUtils .changeOwnerOfTree(rhs, selfSym)).withSpan(selfSym.span))
560561 bindingsBuf += binding
561562 inlining.println(i " proxy at $level: $selfSym = ${bindingsBuf.last}" )
562563 lastSelf = selfSym
563564 lastLevel = level
564565 }
565566 }
566567
568+ /** A list of pairs between TermRefs appearing in thisProxy bindings that
569+ * refer to objects with opaque type aliases and local proxy symbols
570+ * that contain refined versions of these TermRefs where the aliases
571+ * are exposed.
572+ */
573+ private val opaqueProxies = new mutable.ListBuffer [(TermRef , TermRef )]
574+
575+ /** Map first halfs of opaqueProxies pairs to second halfs, using =:= as equality */
576+ def mapRef (ref : TermRef ): Option [TermRef ] =
577+ opaqueProxies.collectFirst {
578+ case (from, to) if from.symbol == ref.symbol && from =:= ref => to
579+ }
580+
581+ /** If `binding` contains TermRefs that refer to objects with opaque
582+ * type aliases, add proxy definitions that expose these aliases
583+ * and substitute such TermRefs with theproxies. Example from pos/opaque-inline1.scala:
584+ *
585+ * object refined:
586+ * opaque type Positive = Int
587+ * inline def Positive(value: Int): Positive = f(value)
588+ * def f(x: Positive): Positive = x
589+ * def run: Unit = { val x = 9; val nine = refined.Positive(x) }
590+ *
591+ * This generates the following proxies:
592+ *
593+ * val $proxy1: refined.type{type Positive = Int} =
594+ * refined.$asInstanceOf$[refined.type{type Positive = Int}]
595+ * val refined$_this: ($proxy1 : refined.type{Positive = Int}) =
596+ * $proxy1
597+ *
598+ * and every reference to `refined` in the inlined expression is replaced by
599+ * `refined_$this`.
600+ */
601+ def accountForOpaques (binding : ValDef )(using Context ): ValDef =
602+ binding.symbol.info.foreachPart {
603+ case ref : TermRef =>
604+ for cls <- ref.widen.classSymbols do
605+ if cls.containsOpaques && mapRef(ref).isEmpty then
606+ def openOpaqueAliases (selfType : Type ): List [(Name , Type )] = selfType match
607+ case RefinedType (parent, rname, TypeAlias (alias)) =>
608+ val opaq = cls.info.member(rname).symbol
609+ if opaq.isOpaqueAlias then
610+ (rname, alias.stripLazyRef.asSeenFrom(ref, cls))
611+ :: openOpaqueAliases(parent)
612+ else Nil
613+ case _ =>
614+ Nil
615+ val refinements = openOpaqueAliases(cls.givenSelfType)
616+ val refinedType = refinements.foldLeft(ref : Type ) ((parent, refinement) =>
617+ RefinedType (parent, refinement._1, TypeAlias (refinement._2))
618+ )
619+ val refiningSym = newSym(InlineBinderName .fresh(), Synthetic , refinedType).asTerm
620+ val refiningDef = ValDef (refiningSym, tpd.ref(ref).cast(refinedType)).withSpan(binding.span)
621+ inlining.println(i " add opaque alias proxy $refiningDef" )
622+ bindingsBuf += refiningDef
623+ opaqueProxies += ((ref, refiningSym.termRef))
624+ case _ =>
625+ }
626+ if opaqueProxies.isEmpty then binding
627+ else
628+ val mapType = new TypeMap :
629+ override def stopAt = StopAt .Package
630+ def apply (t : Type ) = mapOver {
631+ t match
632+ case ref : TermRef => mapRef(ref).getOrElse(ref)
633+ case _ => t
634+ }
635+ binding.symbol.info = mapType(binding.symbol.info)
636+ val mapTree = TreeTypeMap (typeMap = mapType)
637+ mapTree.transform(binding).asInstanceOf [ValDef ]
638+ .showing(i " transformed this binding exposing opaque aliases: $result" , inlining)
639+ end accountForOpaques
640+
567641 private def canElideThis (tpe : ThisType ): Boolean =
568- inlineCallPrefix.tpe == tpe && ctx.owner.isContainedIn(tpe.cls) ||
569- tpe.cls.isContainedIn(inlinedMethod) ||
570- tpe.cls.is(Package )
642+ inlineCallPrefix.tpe == tpe && ctx.owner.isContainedIn(tpe.cls)
643+ || tpe.cls.isContainedIn(inlinedMethod)
644+ || tpe.cls.is(Package )
645+ || tpe.cls.isStaticOwner && ! (tpe.cls.seesOpaques && inlinedMethod.isContainedIn(tpe.cls))
571646
572647 /** Very similar to TreeInfo.isPureExpr, but with the following inliner-only exceptions:
573648 * - synthetic case class apply methods, when the case class constructor is empty, are
@@ -666,12 +741,16 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
666741 case _ =>
667742 }
668743
744+ private val registerTypes = new TypeTraverser :
745+ override def stopAt = StopAt .Package
746+ override def traverse (t : Type ) =
747+ registerType(t)
748+ traverseChildren(t)
749+
669750 /** Register type of leaf node */
670- private def registerLeaf (tree : Tree ): Unit = tree match {
671- case _ : This | _ : Ident | _ : TypeTree =>
672- tree.typeOpt.foreachPart(registerType, stopAtStatic = true )
751+ private def registerLeaf (tree : Tree ): Unit = tree match
752+ case _ : This | _ : Ident | _ : TypeTree => registerTypes.traverse(tree.typeOpt)
673753 case _ =>
674- }
675754
676755 /** Make `tree` part of inlined expansion. This means its owner has to be changed
677756 * from its `originalOwner`, and, if it comes from outside the inlined method
@@ -797,6 +876,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
797876 val inliner = new InlinerMap (
798877 typeMap =
799878 new DeepTypeMap {
879+ override def stopAt =
880+ if opaqueProxies.isEmpty then StopAt .Static else StopAt .Package
800881 def apply (t : Type ) = t match {
801882 case t : ThisType => thisProxy.getOrElse(t.cls, t)
802883 case t : TypeRef => paramProxy.getOrElse(t, mapOver(t))
@@ -915,7 +996,17 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
915996
916997 // Take care that only argument bindings go into `bindings`, since positions are
917998 // different for bindings from arguments and bindings from body.
918- tpd.Inlined (call, finalBindings, finalExpansion)
999+ val res = tpd.Inlined (call, finalBindings, finalExpansion)
1000+ if opaqueProxies.isEmpty then res
1001+ else
1002+ val target =
1003+ if inlinedMethod.is(Transparent ) then call.tpe & res.tpe
1004+ else call.tpe
1005+ res.ensureConforms(target)
1006+ // Make sure that the sealing with the declared type
1007+ // is type correct. Without it we might get problems since the
1008+ // expression's type is the opaque alias but the call's type is
1009+ // the opaque type itself. An example is in pos/opaque-inline1.scala.
9191010 }
9201011 }
9211012
0 commit comments