@@ -556,18 +556,92 @@ 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
578+ .find((from, to) => from.symbol == ref.symbol && from =:= ref)
579+ .map(_._2)
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 )
571645
572646 /** Very similar to TreeInfo.isPureExpr, but with the following inliner-only exceptions:
573647 * - 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) {
666740 case _ =>
667741 }
668742
743+ private val registerTypes = new TypeTraverser :
744+ override def stopAt = StopAt .Package
745+ // Only register ThisType prefixes that see opaques. No need to register the others
746+ // since they are static prefixes.
747+ def registerStaticPrefix (t : Type ): Unit = t match
748+ case t : ThisType if t.cls.seesOpaques => registerType(t)
749+ case t : NamedType => registerStaticPrefix(t.prefix)
750+ case _ =>
751+ override def traverse (t : Type ) = t match
752+ case t : NamedType if t.currentSymbol.isStatic =>
753+ registerStaticPrefix(t.prefix)
754+ case t =>
755+ registerType(t)
756+ traverseChildren(t)
757+
669758 /** Register type of leaf node */
670- private def registerLeaf (tree : Tree ): Unit = tree match {
671- case _ : This | _ : Ident | _ : TypeTree =>
672- tree.typeOpt.foreachPart(registerType, StopAt .Static )
759+ private def registerLeaf (tree : Tree ): Unit = tree match
760+ case _ : This | _ : Ident | _ : TypeTree => registerTypes.traverse(tree.typeOpt)
673761 case _ =>
674- }
675762
676763 /** Make `tree` part of inlined expansion. This means its owner has to be changed
677764 * 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) {
797884 val inliner = new InlinerMap (
798885 typeMap =
799886 new DeepTypeMap {
887+ override def stopAt =
888+ if opaqueProxies.isEmpty then StopAt .Static else StopAt .Package
800889 def apply (t : Type ) = t match {
801890 case t : ThisType => thisProxy.getOrElse(t.cls, t)
802891 case t : TypeRef => paramProxy.getOrElse(t, mapOver(t))
@@ -843,7 +932,16 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
843932
844933 // Apply inliner to `rhsToInline`, split off any implicit bindings from result, and
845934 // make them part of `bindingsBuf`. The expansion is then the tree that remains.
846- val expansion = inliner.transform(rhsToInline)
935+ val expansion0 = inliner.transform(rhsToInline)
936+ val expansion =
937+ if opaqueProxies.nonEmpty && ! inlinedMethod.is(Transparent ) then
938+ expansion0.cast(call.tpe)(using ctx.withSource(expansion0.source))
939+ // the cast makes sure that the sealing with the declared type
940+ // is type correct. Without it we might get problems since the
941+ // expression's type is the opaque alias but the call's type is
942+ // the opaque type itself. An example is in pos/opaque-inline1.scala.
943+ else
944+ expansion0
847945
848946 def issueError () = callValueArgss match {
849947 case (msgArg :: Nil ) :: Nil =>
0 commit comments