Skip to content

Partial fix for inlining with opaque types #6846

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@ object SymDenotations {

def seesOpaques(implicit ctx: Context): Boolean =
containsOpaques ||
is(Module, butNot = Package) && owner.containsOpaques
is(Module, butNot = Package) && owner.seesOpaques

/** Is this the denotation of a self symbol of some class?
* This is the case if one of two conditions holds:
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2463,9 +2463,9 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
if (skipped) op
else {
indent += 2
b append "\n" append (" " * indent) append "==> " append str
b.append("\n").append(" " * indent).append("==> ").append(str)
val res = op
b append "\n" append (" " * indent) append "<== " append str append " = " append show(res)
b.append("\n").append(" " * indent).append("<== ").append(str).append(" = ").append(show(res))
indent -= 2
res
}
Expand Down
143 changes: 90 additions & 53 deletions compiler/src/dotty/tools/dotc/typer/Inliner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {

private val (methPart, callTypeArgs, callValueArgss) = decomposeCall(call)
private val inlinedMethod = methPart.symbol
private val inlineCallPrefix = qualifier(methPart)
private val inlineCallPrefix =
qualifier(methPart).orElse(This(inlinedMethod.enclosingClass.asClass))

// Make sure all type arguments to the call are fully determined
for (targ <- callTypeArgs) fullyDefinedType(targ.tpe, "inlined type argument", targ.span)
Expand Down Expand Up @@ -243,6 +244,75 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {
private def newSym(name: Name, flags: FlagSet, info: Type)(implicit ctx: Context): Symbol =
ctx.newSymbol(ctx.owner, name, flags, info, coord = call.span)

/** A this-proxy for `tpe` is not needed if
* - `tpe` is the type of the inline call receiver and the inline goes to the same class.
* Example:
*
* class C {
* inline def f()
* this.f()
* }
*
* - The class referenced by `tpe` is inside the inline method
* - The class referenced by `tpe` is a static object that does not have opaque types
* in its scope or environment
*/
private def canElideThis(tpe: ThisType): Boolean = {
val cls = tpe.cls
inlineCallPrefix.tpe == tpe && ctx.owner.isContainedIn(cls) ||
cls.isContainedIn(inlinedMethod) ||
cls.isStaticOwner && !cls.seesOpaques
}

/** Populate `thisProxy` and some parts of `paramProxy` as follows:
*
* 1. If given type refers to a this type of a class that cannot be elided,
* create a proxy symbol and bind the thistype to refer to the proxy.
* The proxy is not yet entered in `bindingsBuf`; that will come later.
* 2. If this type goes to a class or non-static module, also create a this proxy
* for the this type of the owner of the inline method, so that we have a root
* to compute the initializers of this-proxies.
* 3. If the class referenced by a this type has type parameters, create parameter
* proxies for them.
*/
private def registerThisProxies(tpe: Type): Unit = tpe match {
case tpe: ThisType if !canElideThis(tpe) && !thisProxy.contains(tpe.cls) =>
def adaptToPrefix(tp: Type) = tp.asSeenFrom(inlineCallPrefix.tpe, inlinedMethod.owner)
val proxyName = s"${tpe.cls.name}_this".toTermName
val proxyType = inlineCallPrefix.tpe.dealias.tryNormalize match {
case typeMatchResult if typeMatchResult.exists => typeMatchResult
case _ => adaptToPrefix(tpe).widenIfUnstable
}
thisProxy(tpe.cls) = newSym(proxyName, InlineProxy, proxyType).termRef
if (!tpe.cls.isStaticOwner)
registerThisProxies(inlinedMethod.owner.thisType) // make sure we have a base from which to outer-select
for (param <- tpe.cls.typeParams)
paramProxy(param.typeRef) = adaptToPrefix(param.typeRef)
case _ =>
}

/** Populate the rest of `paramProxy` as follows:
*
* If the given type refers to a parameter, make `paramProxy` refer to the entry stored
* in `paramNames` under the parameter's name. This roundabout way to bind parameter
* references to proxies is done because we don't know a priori what the parameter
* references of a method are (we only know the method's type, but that contains TypeParamRefs
* and MethodParams, not TypeRefs or TermRefs.
*/
private def registerParamProxies(tpe: Type): Unit = tpe match {
case tpe: NamedType
if tpe.symbol.is(Param) && tpe.symbol.owner == inlinedMethod && !paramProxy.contains(tpe) =>
paramProxy(tpe) = paramBinding(tpe.name)
case _ =>
}

/** Perform `op` for each leaf tree (of type This, Ident, or TypeTree) of rhsToInline` */
private def foreachRHSLeaf(op: Type => Unit): Unit =
rhsToInline.foreachSubTree {
case tree: (This | Ident | TypeTree) => tree.tpe.foreachPart(op)
case _ =>
}

/** A binding for the parameter of an inline method. This is a `val` def for
* by-value parameters and a `def` def for by-name parameters. `val` defs inherit
* inline annotations from their parameters. The generated `def` is appended
Expand Down Expand Up @@ -312,65 +382,26 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {
var lastSelf: Symbol = NoSymbol
var lastLevel: Int = 0
for ((level, selfSym) <- sortedProxies) {
lazy val rhsClsSym = selfSym.info.widenDealias.classSymbol
def widenSelfInfo(tp: Type): Type = tp.widenDealias match {
case RefinedType(parent, _, _) => widenSelfInfo(parent)
case tpw => tpw
}
val rhsClsSym = widenSelfInfo(selfSym.info).classSymbol
val rhs =
if (lastSelf.exists)
ref(lastSelf).outerSelect(lastLevel - level, selfSym.info)
else if (rhsClsSym.is(Module) && rhsClsSym.isStatic)
if (rhsClsSym.is(Module) && rhsClsSym.isStatic)
ref(rhsClsSym.sourceModule)
else if (lastSelf.exists)
ref(lastSelf).outerSelect(lastLevel - level, selfSym.info)
else
inlineCallPrefix
val binding = ValDef(selfSym.asTerm, rhs).withSpan(selfSym.span).setDefTree
val binding = ValDef(selfSym.asTerm, rhs.ensureConforms(selfSym.info)).withSpan(selfSym.span).setDefTree
bindingsBuf += binding
inlining.println(i"proxy at $level: $selfSym = ${bindingsBuf.last}")
lastSelf = selfSym
lastLevel = level
}
}

private def canElideThis(tpe: ThisType): Boolean =
inlineCallPrefix.tpe == tpe && ctx.owner.isContainedIn(tpe.cls) ||
tpe.cls.isContainedIn(inlinedMethod) ||
tpe.cls.is(Package)

/** Populate `thisProxy` and `paramProxy` as follows:
*
* 1a. If given type refers to a static this, thisProxy binds it to corresponding global reference,
* 1b. If given type refers to an instance this to a class that is not contained in the
* inline method, create a proxy symbol and bind the thistype to refer to the proxy.
* The proxy is not yet entered in `bindingsBuf`; that will come later.
* 2. If given type refers to a parameter, make `paramProxy` refer to the entry stored
* in `paramNames` under the parameter's name. This roundabout way to bind parameter
* references to proxies is done because we don't know a priori what the parameter
* references of a method are (we only know the method's type, but that contains TypeParamRefs
* and MethodParams, not TypeRefs or TermRefs.
*/
private def registerType(tpe: Type): Unit = tpe match {
case tpe: ThisType if !canElideThis(tpe) && !thisProxy.contains(tpe.cls) =>
val proxyName = s"${tpe.cls.name}_this".toTermName
def adaptToPrefix(tp: Type) = tp.asSeenFrom(inlineCallPrefix.tpe, inlinedMethod.owner)
val proxyType = inlineCallPrefix.tpe.dealias.tryNormalize match {
case typeMatchResult if typeMatchResult.exists => typeMatchResult
case _ => adaptToPrefix(tpe).widenIfUnstable
}
thisProxy(tpe.cls) = newSym(proxyName, InlineProxy, proxyType).termRef
if (!tpe.cls.isStaticOwner)
registerType(inlinedMethod.owner.thisType) // make sure we have a base from which to outer-select
for (param <- tpe.cls.typeParams)
paramProxy(param.typeRef) = adaptToPrefix(param.typeRef)
case tpe: NamedType
if tpe.symbol.is(Param) && tpe.symbol.owner == inlinedMethod && !paramProxy.contains(tpe) =>
paramProxy(tpe) = paramBinding(tpe.name)
case _ =>
}

/** Register type of leaf node */
private def registerLeaf(tree: Tree): Unit = tree match {
case _: This | _: Ident | _: TypeTree =>
tree.tpe.foreachPart(registerType, stopAtStatic = true)
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
* itself, it has to be marked as an inlined argument.
Expand Down Expand Up @@ -401,14 +432,18 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {
)
}

// make sure prefix is executed if it is impure
if (!isIdempotentExpr(inlineCallPrefix))
registerThisProxies(inlinedMethod.owner.thisType)

// Compute this proxies for all leaves of the inlined body
foreachRHSLeaf(registerThisProxies)

// Compute bindings for all parameters, appending them to bindingsBuf
computeParamBindings(inlinedMethod.info, callTypeArgs, callValueArgss)

// make sure prefix is executed if it is impure
if (!isIdempotentExpr(inlineCallPrefix)) registerType(inlinedMethod.owner.thisType)

// Register types of all leaves of inlined body so that the `paramProxy` and `thisProxy` maps are defined.
rhsToInline.foreachSubTree(registerLeaf)
foreachRHSLeaf(registerParamProxies)

// Compute bindings for all this-proxies, appending them to bindingsBuf
computeThisBindings()
Expand All @@ -427,9 +462,11 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {
val inliner = new TreeTypeMap(
typeMap =
new TypeMap {
override val stopAtStatic = false
def apply(t: Type) = t match {
case t: ThisType => thisProxy.getOrElse(t.cls, t)
case t: TypeRef => paramProxy.getOrElse(t, mapOver(t))
case t: TermRef if t.symbol.is(Package) => t
case t: SingletonType => paramProxy.getOrElse(t, mapOver(t))
case t => mapOver(t)
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ object Typer {
*/
enum BindingPrec {
case NothingBound, PackageClause, WildImport, NamedImport, Definition

def isImportPrec = this == NamedImport || this == WildImport
}

Expand Down
3 changes: 3 additions & 0 deletions compiler/test/dotc/pos-from-tasty.blacklist
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ t3612.scala

# Other failure
t802.scala

# Recursion limit exceeded: find-member K0.ToUnion
shapeless.scala
Loading