From 1c2a8ded160fbf7609095f04dfb208796d4691bd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 24 Jun 2021 12:15:14 +0200 Subject: [PATCH] Map opaque types in arguments of inlined calls to proxies An argument of an inlined call might have a type that refers to opaque types in the inlined method's scope. In that case, we need to cast those arguments to types where the opaque references are replaced by proxies, so that the opaque aliases are visible when accessing the argument. Fixes #12914 --- .../src/dotty/tools/dotc/typer/Inliner.scala | 105 ++++++++++++------ tests/run/i12914.check | 8 ++ tests/run/i12914.scala | 27 +++++ 3 files changed, 103 insertions(+), 37 deletions(-) create mode 100644 tests/run/i12914.check create mode 100644 tests/run/i12914.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 630e29101b03..2068b9102899 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -409,7 +409,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { private val methPart = funPart(call) private val callTypeArgs = typeArgss(call).flatten - private val callValueArgss = termArgss(call) + private val rawCallValueArgss = termArgss(call) private val inlinedMethod = methPart.symbol private val inlineCallPrefix = qualifier(methPart).orElse(This(inlinedMethod.enclosingClass.asClass)) @@ -581,31 +581,17 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { 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 - * 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`. + /** If `tp` contains TermRefs that refer to objects with opaque + * type aliases, add proxy definitions to `opaqueProxies` that expose these aliases. */ - def accountForOpaques(binding: ValDef)(using Context): ValDef = - binding.symbol.info.foreachPart { + def addOpaqueProxies(tp: Type, span: Span, forThisProxy: Boolean)(using Context): Unit = + tp.foreachPart { case ref: TermRef => for cls <- ref.widen.classSymbols do - if cls.containsOpaques && mapRef(ref).isEmpty then + if cls.containsOpaques + && (forThisProxy || inlinedMethod.isContainedIn(cls)) + && 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 @@ -620,27 +606,67 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { 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") + val refiningDef = ValDef(refiningSym, tpd.ref(ref).cast(refinedType)).withSpan(span) + inlining.println(i"add opaque alias proxy $refiningDef for $ref in $tp") bindingsBuf += refiningDef opaqueProxies += ((ref, refiningSym.termRef)) case _ => } + + /** Map all TermRefs that match left element in `opaqueProxies` to the + * corresponding right element. + */ + val mapOpaques = TreeTypeMap( + typeMap = new TypeMap: + override def stopAt = StopAt.Package + def apply(t: Type) = mapOver { + t match + case ref: TermRef => mapRef(ref).getOrElse(ref) + case _ => t + } + ) + + /** 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 = + addOpaqueProxies(binding.symbol.info, binding.span, forThisProxy = true) 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] + binding.symbol.info = mapOpaques.typeMap(binding.symbol.info) + mapOpaques.transform(binding).asInstanceOf[ValDef] .showing(i"transformed this binding exposing opaque aliases: $result", inlining) end accountForOpaques + /** If value argument contains references to objects that contain opaque types, + * map them to their opaque proxies. + */ + def mapOpaquesInValueArg(arg: Tree)(using Context): Tree = + val argType = arg.tpe.widen + addOpaqueProxies(argType, arg.span, forThisProxy = false) + if opaqueProxies.nonEmpty then + val mappedType = mapOpaques.typeMap(argType) + if mappedType ne argType then arg.cast(AndType(arg.tpe, mappedType)) + else arg + else arg + private def canElideThis(tpe: ThisType): Boolean = inlineCallPrefix.tpe == tpe && ctx.owner.isContainedIn(tpe.cls) || tpe.cls.isContainedIn(inlinedMethod) @@ -773,7 +799,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { def inlined(sourcePos: SrcPos): Tree = { // Special handling of `requireConst` and `codeOf` - callValueArgss match + rawCallValueArgss match case (arg :: Nil) :: Nil => if inlinedMethod == defn.Compiletime_requireConst then arg match @@ -823,6 +849,11 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { case TypeApply(fn, _) => paramTypess(fn, acc) case _ => acc + val callValueArgss = rawCallValueArgss.nestedMapConserve(mapOpaquesInValueArg) + + if callValueArgss ne rawCallValueArgss then + inlining.println(i"mapped value args = ${callValueArgss.flatten}%, %") + // Compute bindings for all parameters, appending them to bindingsBuf if !computeParamBindings(inlinedMethod.info, callTypeArgs, callValueArgss, paramTypess(call, Nil)) then return call @@ -1254,7 +1285,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { case fail: Implicits.SearchFailureType => false case _ => - //inliner.println(i"inferred implicit $sym: ${sym.info} with $evidence: ${evidence.tpe.widen}, ${evCtx.gadt.constraint}, ${evCtx.typerState.constraint}") + //inlining.println(i"inferred implicit $sym: ${sym.info} with $evidence: ${evidence.tpe.widen}, ${evCtx.gadt.constraint}, ${evCtx.typerState.constraint}") newTermBinding(sym, evidence) true } diff --git a/tests/run/i12914.check b/tests/run/i12914.check new file mode 100644 index 000000000000..9fe584d62718 --- /dev/null +++ b/tests/run/i12914.check @@ -0,0 +1,8 @@ +ASD +asd +ASD +asd +ASD +asd +aSdaSdaSd +aSdaSdaSd diff --git a/tests/run/i12914.scala b/tests/run/i12914.scala new file mode 100644 index 000000000000..c60937916149 --- /dev/null +++ b/tests/run/i12914.scala @@ -0,0 +1,27 @@ + +class opq: + opaque type Str = java.lang.String + object Str: + def apply(s: String): Str = s + inline def lower(s: Str): String = s.toLowerCase + extension (s: Str) + transparent inline def upper: String = s.toUpperCase + inline def concat(xs: List[Str]): Str = String(xs.flatten.toArray) + transparent inline def concat2(xs: List[Str]): Str = String(xs.flatten.toArray) + + +@main def Test = + val opq = new opq() + import opq.* + val a: Str = Str("aSd") + println(a.upper) + println(opq.lower(a)) + def b: Str = Str("aSd") + println(b.upper) + println(opq.lower(b)) + def c(): Str = Str("aSd") + println(c().upper) + println(opq.lower(c())) + println(opq.concat(List(a, b, c()))) + println(opq.concat2(List(a, b, c()))) +