dotty.tools.dotc.transform.Bridges.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scala3-compiler_3 Show documentation
Show all versions of scala3-compiler_3 Show documentation
scala3-compiler-bootstrapped
package dotty.tools
package dotc
package transform
import core.*
import Symbols.*, Types.*, Contexts.*, Decorators.*, Flags.*, Scopes.*, Phases.*
import DenotTransformers.*
import ast.untpd
import collection.{mutable, immutable}
import util.SrcPos
import ContextFunctionResults.{contextResultCount, contextFunctionResultTypeAfter}
import StdNames.nme
import Constants.Constant
import TypeErasure.transformInfo
import Erasure.Boxing.adaptClosure
/** A helper class for generating bridge methods in class `root`. */
class Bridges(root: ClassSymbol, thisPhase: DenotTransformer)(using Context) {
import ast.tpd.*
assert(ctx.phase == erasurePhase.next)
private val preErasureCtx = ctx.withPhase(erasurePhase)
private lazy val elimErasedCtx = ctx.withPhase(elimErasedValueTypePhase.next)
private class BridgesCursor(using Context) extends OverridingPairs.Cursor(root) {
override def isSubParent(parent: Symbol, bc: Symbol)(using Context) =
true
// Never consider a bridge if there is a superclass that would contain it
// See run/t2857.scala for a test that would break with a VerifyError otherwise.
/** Only use the superclass of `root` as a parent class. This means
* overriding pairs that have a common implementation in a trait parent
* are also counted. This is necessary because we generate bridge methods
* only in classes, never in traits.
*/
override def parents = Array(root.superClass)
override def exclude(sym: Symbol) =
!sym.isOneOf(MethodOrModule) || sym.isAllOf(Module | JavaDefined) || super.exclude(sym)
override def canBeHandledByParent(sym1: Symbol, sym2: Symbol, parent: Symbol): Boolean =
OverridingPairs.isOverridingPair(sym1, sym2, parent.thisType)
}
val site = root.thisType
private var toBeRemoved = immutable.Set[Symbol]()
private val bridges = mutable.ListBuffer[Tree]()
private val bridgesScope = newScope
private val bridgeTarget = MutableSymbolMap[Symbol]()
def bridgePosFor(member: Symbol): SrcPos =
(if (member.owner == root && member.span.exists) member else root).srcPos
/** Add a bridge between `member` and `other`, where `member` overrides `other`
* before erasure, if the following conditions are satisfied.
*
* - `member` and `other` have different signatures
* - there is not yet a bridge with the same name and signature in `root`.
*
* The bridge has the erased info of `other` and forwards to `member`.
* Additionally, if `member` and `other` do have the same signature,
* but not the same type after erasure and before elimErasedValueTypes
* issue an error: A bridge would be needed yet it would clash with the member itself.
* See neg/i1905.scala
*/
private def addBridgeIfNeeded(member: Symbol, other: Symbol) = {
def bridgeExists =
bridgesScope.lookupAll(member.name).exists(bridge =>
bridgeTarget(bridge) == member && bridge.signature == other.signature)
def info(sym: Symbol)(using Context) = sym.info
def desc(sym: Symbol)= {
val infoStr = info(sym)(using preErasureCtx) match {
case ExprType(info) => i": $info"
case info => info.show
}
i"$sym$infoStr in ${sym.owner}"
}
if (member.signature == other.signature) {
if (!member.info.matches(other.info))
report.error(em"""bridge generated for member ${desc(member)}
|which overrides ${desc(other)}
|clashes with definition of the member itself; both have erased type ${info(member)(using elimErasedCtx)}."""",
bridgePosFor(member))
}
else if !inContext(preErasureCtx)(site.memberInfo(member).matches(site.memberInfo(other))) then
// Neither symbol signatures nor pre-erasure types seen from root match; this means
// according to Scala 2 semantics there is no override.
// A bridge might introduce a classcast exception.
// Example where this was observed: run/i12828a.scala and MapView in stdlib213
report.log(i"suppress bridge in $root for ${member} in ${member.owner} and ${other.showLocated} since member infos ${site.memberInfo(member)} and ${site.memberInfo(other)} do not match")
else if !bridgeExists then
addBridge(member, other)
}
/** Generate bridge between `member` and `other`
*/
private def addBridge(member: Symbol, other: Symbol) = {
val bridge = other.copy(
owner = root,
flags = (member.flags | Method | Bridge | Artifact) &~
(Accessor | ParamAccessor | CaseAccessor | Deferred | Lazy | Module),
coord = bridgePosFor(member).span).enteredAfter(thisPhase).asTerm
report.debuglog(
i"""generating bridge from ${other.showLocated}: ${other.info}
|to ${member.showLocated}: ${member.info} @ ${member.span}
|bridge: ${bridge.showLocated} with flags: ${bridge.flagsString}""")
bridgeTarget(bridge) = member
bridgesScope.enter(bridge)
if (other.owner == root) {
root.delete(other)
toBeRemoved += other
}
val memberCount = contextResultCount(member)
/** Eta expand application `ref(args)` as needed.
* To do this correctly, we have to look at the member's original pre-erasure
* type and figure out which context function types in its result are
* not yet instantiated.
*/
def etaExpand(ref: Tree, args: List[Tree])(using Context): Tree =
def expand(args: List[Tree], tp: Type, n: Int)(using Context): Tree =
if n <= 0 then
assert(ctx.typer.isInstanceOf[Erasure.Typer])
ctx.typer.typed(untpd.cpy.Apply(ref)(ref, args), member.info.finalResultType)
else
val mtWithoutErasedParams = atPhase(erasurePhase) {
val defn.ContextFunctionType(argTypes, resType) = tp.dealias: @unchecked
val paramInfos = argTypes.filterNot(_.hasAnnotation(defn.ErasedParamAnnot))
MethodType(paramInfos, resType)
}
val anonFun = newAnonFun(ctx.owner, mtWithoutErasedParams, coord = ctx.owner.coord)
anonFun.info = transformInfo(anonFun, anonFun.info)
def lambdaBody(refss: List[List[Tree]]) =
val refs :: Nil = refss: @unchecked
val expandedRefs = refs.map(_.withSpan(ctx.owner.span.endPos)) match
case (bunchedParam @ Ident(nme.ALLARGS)) :: Nil =>
mtWithoutErasedParams.paramInfos.indices.toList.map(n =>
bunchedParam
.select(nme.primitive.arrayApply)
.appliedTo(Literal(Constant(n))))
case refs1 => refs1
expand(args ::: expandedRefs, mtWithoutErasedParams.resType, n - 1)(using ctx.withOwner(anonFun))
val unadapted = Closure(anonFun, lambdaBody)
cpy.Block(unadapted)(unadapted.stats,
adaptClosure(unadapted.expr.asInstanceOf[Closure]))
end expand
val otherCount = contextResultCount(other)
val start = contextFunctionResultTypeAfter(member, otherCount)(using preErasureCtx)
expand(args, start, memberCount - otherCount)(using ctx.withOwner(bridge))
end etaExpand
def bridgeRhs(argss: List[List[Tree]]) =
assert(argss.tail.isEmpty)
val ref = This(root).select(member)
if member.info.isParameterless then ref // can happen if `member` is a module
else if memberCount == 0 then ref.appliedToTermArgs(argss.head)
else etaExpand(ref, argss.head)
bridges += DefDef(bridge, bridgeRhs(_).withSpan(bridge.span))
}
/** Add all necessary bridges to template statements `stats`, and remove at the same
* time deferred methods in `stats` that are replaced by a bridge with the same signature.
*/
def add(stats: List[untpd.Tree]): List[untpd.Tree] =
val opc = inContext(preErasureCtx) { new BridgesCursor }
while opc.hasNext do
if !opc.overriding.is(Deferred) then
addBridgeIfNeeded(opc.overriding, opc.overridden)
opc.next()
if bridges.isEmpty then stats
else stats.filterNot(stat => toBeRemoved contains stat.symbol) ::: bridges.toList
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy