scala.tools.nsc.ast.TreeGen.scala Maven / Gradle / Ivy
The newest version!
/*
* Scala (https://www.scala-lang.org)
*
* Copyright EPFL and Lightbend, Inc.
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/
package scala.tools.nsc
package ast
import scala.annotation.{tailrec, unused}
import scala.collection.mutable.ListBuffer
import scala.reflect.internal.util.{FreshNameCreator, ListOfNil}
import scala.util.chaining._
import symtab.Flags._
/** XXX to resolve: TreeGen only assumes global is a SymbolTable, but
* TreeDSL at the moment expects a Global. Can we get by with SymbolTable?
*/
abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL {
val global: Global
import global._
import definitions._
/** Builds a fully attributed, synthetic wildcard import node.
*/
def mkWildcardImport(pkg: Symbol): Import =
mkImportFromSelector(pkg, ImportSelector.wildList)
/** Builds a fully attributed, synthetic import node.
* import `qualSym`.{`name` => `toName`}
*/
def mkImport(qualSym: Symbol, name: Name, toName: Name): Import =
mkImportFromSelector(qualSym, ImportSelector(name, 0, toName, 0) :: Nil)
def mkImportFromSelector(qualSym: Symbol, selector: List[ImportSelector]): Import = {
assert(qualSym ne null, this)
val qual = gen.mkAttributedStableRef(qualSym)
val importSym = (
NoSymbol
newImport NoPosition
setFlag SYNTHETIC
setInfo ImportType(qual)
)
val importTree = (
Import(qual, selector)
setSymbol importSym
setType NoType
)
importTree
}
// wrap the given expression in a SoftReference so it can be gc-ed
def mkSoftRef(expr: Tree): Tree = atPos(expr.pos) {
val constructor = SoftReferenceClass.info.nonPrivateMember(nme.CONSTRUCTOR).suchThat(_.paramss.flatten.size == 1)
NewFromConstructor(constructor, expr)
}
// Builds a tree of the form "{ lhs = rhs ; lhs }"
def mkAssignAndReturn(lhs: Symbol, rhs: Tree): Tree = {
def lhsRef = if (lhs.owner.isClass) Select(This(lhs.owner), lhs) else Ident(lhs)
Block(Assign(lhsRef, rhs) :: Nil, lhsRef)
}
def newModule(accessor: Symbol, tpe: Type) = {
val ps = tpe.typeSymbol.primaryConstructor.info.paramTypes
if (ps.isEmpty) New(tpe)
else New(tpe, This(accessor.owner.enclClass))
}
def mkRuntimeCall(meth: Name, args: List[Tree]): Tree =
mkRuntimeCall(meth, Nil, args)
def mkRuntimeCall(meth: Name, targs: List[Type], args: List[Tree]): Tree =
mkMethodCall(ScalaRunTimeModule, meth, targs, args)
def mkThrowNewRuntimeException(message: String) =
Throw(RuntimeExceptionClass.tpe, Literal(Constant(message)))
/** A creator for a call to a scala.reflect.Manifest or ClassManifest factory method.
*
* @param full full or partial manifest (target will be Manifest or ClassManifest)
* @param constructor name of the factory method (e.g. "classType")
* @param tparg the type argument
* @param args value arguments
* @return the tree
*/
def mkManifestFactoryCall(full: Boolean, constructor: String, tparg: Type, args: List[Tree]): Tree =
mkMethodCall(
if (full) FullManifestModule else PartialManifestModule,
newTermName(constructor),
List(tparg),
args
)
/** Make a synchronized block on 'monitor'. */
def mkSynchronized(monitor: Tree)(body: Tree): Tree =
Apply(Select(monitor, Object_synchronized), List(body))
def mkAppliedTypeForCase(clazz: Symbol): Tree = {
val numParams = clazz.typeParams.size
if (clazz.typeParams.isEmpty) Ident(clazz)
else AppliedTypeTree(Ident(clazz), (1 to numParams).map(_ => Bind(tpnme.WILDCARD, EmptyTree)).toList)
}
def mkBindForCase(patVar: Symbol, clazz: Symbol, targs: List[Type]): Tree = {
Bind(patVar, Typed(Ident(nme.WILDCARD),
if (targs.isEmpty) mkAppliedTypeForCase(clazz)
else AppliedTypeTree(Ident(clazz), targs map TypeTree)
))
}
def wildcardStar(tree: Tree) =
atPos(tree.pos) { Typed(tree, Ident(tpnme.WILDCARD_STAR)) }
def paramToArg(vparam: Symbol): Tree =
paramToArg(Ident(vparam), isRepeatedParamType(vparam.tpe))
def paramToArg(vparam: ValDef): Tree =
paramToArg(Ident(vparam.name), treeInfo.isRepeatedParamType(vparam.tpt))
def paramToArg(arg: Ident, isRepeatedParam: Boolean): Tree =
if (isRepeatedParam) wildcardStar(arg) else arg
/** Make forwarder to method `target`, passing all parameters in `params` */
def mkForwarder(target: Tree, vparamss: List[List[Symbol]]) =
vparamss.foldLeft(target)((fn, vparams) => Apply(fn, vparams map paramToArg))
/** Applies a wrapArray call to an array, making it an immutable ArraySeq suitable for Scala varargs.
* Don't let a reference type parameter be inferred, in case it's a singleton: apply the element type directly.
*/
def mkWrapVarargsArray(tree: Tree, elemtp: Type) = {
mkMethodCall(
wrapVarargsArrayMethod(elemtp),
if (isPrimitiveValueType(elemtp)) Nil else List(elemtp),
List(tree)
)
}
/** Cast `tree` to type `pt` by creating
* one of the calls of the form
*
* x.asInstanceOf[`pt`] up to phase uncurry
* x.asInstanceOf[`pt`]() if after uncurry but before erasure
* x.\$asInstanceOf[`pt`]() if at or after erasure
*/
override def mkCast(tree: Tree, pt: Type): Tree = {
debuglog("casting " + tree + ":" + tree.tpe + " to " + pt + " at phase: " + phase)
atPos(tree.pos) {
mkAsInstanceOf(tree, pt, any = !phase.next.erasedTypes, wrapInApply = isAtPhaseAfter(currentRun.uncurryPhase))
}
}
// drop annotations generated by CPS plugin etc, since its annotationchecker rejects T @cps[U] <: Any
// let's assume for now annotations don't affect casts, drop them there, and bring them back using the outer Typed tree
def mkCastPreservingAnnotations(tree: Tree, pt: Type) =
Typed(mkCast(tree, pt.withoutAnnotations), TypeTree(pt))
/** Generate a cast for tree Tree representing Array with
* elem type elemtp to expected type pt.
*/
def mkCastArray(tree: Tree, elemtp: Type, pt: Type) =
if (elemtp.typeSymbol == AnyClass && isPrimitiveValueType(tree.tpe.typeArgs.head))
mkCast(mkRuntimeCall(nme.toObjectArray, List(tree)), pt)
else
mkCast(tree, pt)
/** Translate names in Select/Ident nodes to type names.
*/
def convertToTypeName(tree: Tree): Option[RefTree] = tree match {
case Select(qual, name) => Some(Select(qual, name.toTypeName).setAttachments(tree.attachments))
case Ident(name) => Some(Ident(name.toTypeName).setAttachments(tree.attachments))
case _ => None
}
/** Try to convert Select(qual, name) to a SelectFromTypeTree.
*/
def convertToSelectFromType(qual: Tree, origName: Name) = convertToTypeName(qual) match {
case Some(qual1) => SelectFromTypeTree(qual1 setPos qual.pos, origName.toTypeName)
case _ => EmptyTree
}
/** Create a ValDef initialized to the given expression, setting the
* symbol to its packed type, and an function for creating Idents
* which refer to it.
*/
private def mkPackedValDef(expr: Tree, owner: Symbol, name: Name): (ValDef, () => Ident) = {
val packedType = typer.packedType(expr, owner)
val sym = owner.newValue(name.toTermName, expr.pos.makeTransparent, SYNTHETIC) setInfo packedType
(ValDef(sym, expr), () => Ident(sym) setPos sym.pos.focus setType expr.tpe)
}
/** Used in situations where you need to access value of an expression several times
*/
def evalOnce(expr: Tree, owner: Symbol, unit: CompilationUnit)(within: (() => Tree) => Tree): Tree = evalOnce(expr, owner, unit.fresh)(within)
def evalOnce(expr: Tree, owner: Symbol, fresh: FreshNameCreator)(within: (() => Tree) => Tree): Tree = {
var used = false
if (treeInfo.isExprSafeToInline(expr)) {
within(() => if (used) expr.duplicate else { used = true; expr })
}
else {
val (valDef, identFn) = mkPackedValDef(expr, owner, freshTermName("ev$")(fresh))
val containing = within(identFn)
ensureNonOverlapping(containing, List(expr))
Block(List(valDef), containing) setPos (containing.pos union expr.pos)
}
}
def evalOnceAll(exprs: List[Tree], owner: Symbol, unit: CompilationUnit)(within: (List[() => Tree]) => Tree): Tree =
evalOnceAll(exprs, owner, unit.fresh)(within)
def evalOnceAll(exprs: List[Tree], owner: Symbol, fresh: FreshNameCreator)(within: (List[() => Tree]) => Tree): Tree = {
val vdefs = new ListBuffer[ValDef]
val exprs1 = new ListBuffer[() => Tree]
val used = new Array[Boolean](exprs.length)
var i = 0
for (expr <- exprs) {
if (treeInfo.isExprSafeToInline(expr)) {
exprs1 += {
val idx = i
() => if (used(idx)) expr.duplicate else { used(idx) = true; expr }
}
}
else {
val (valDef, identFn) = mkPackedValDef(expr, owner, freshTermName("ev$")(fresh))
vdefs += valDef
exprs1 += identFn
}
i += 1
}
val prefix = vdefs.toList
val containing = within(exprs1.toList)
ensureNonOverlapping(containing, exprs)
if (prefix.isEmpty) containing
else Block(prefix, containing) setPos (prefix.head.pos union containing.pos)
}
/** Creates a tree representing new Object { stats }.
* To make sure an anonymous subclass of Object is created,
* if there are no stats, a () is added.
*/
def mkAnonymousNew(stats: List[Tree]): Tree = {
val stats1 = if (stats.isEmpty) List(Literal(Constant(()))) else stats
mkNew(Nil, noSelfType, stats1, NoPosition, NoPosition)
}
// Construct a method to implement `fun`'s single abstract method (`apply`, when `fun.tpe` is a built-in function type)
def mkMethodFromFunction(localTyper: analyzer.Typer)(owner: Symbol, fun: Function) = {
// TODO: treat FunctionN like any other SAM -- drop `&& !isFunctionType(fun.tpe)`
val sam = if (!isFunctionType(fun.tpe)) samOf(fun.tpe) else NoSymbol
if (!sam.exists) mkMethodForFunctionBody(localTyper)(owner, fun, nme.apply)()
else {
val samMethType = fun.tpe memberInfo sam
mkMethodForFunctionBody(localTyper)(owner, fun, sam.name.toTermName)(methParamProtos = samMethType.params, resTp = samMethType.resultType)
}
}
// used to create the lifted method that holds a function's body
def mkLiftedFunctionBodyMethod(localTyper: global.analyzer.Typer)(owner: global.Symbol, fun: global.Function) = {
@tailrec
def nonLocalEnclosingMember(sym: Symbol): Symbol = {
if (sym.isLocalDummy) sym.enclClass.primaryConstructor
else if (sym.isLocalToBlock) nonLocalEnclosingMember(sym.originalOwner)
else sym
}
val ownerName = nonLocalEnclosingMember(fun.symbol.originalOwner).name match {
case nme.CONSTRUCTOR => nme.NEWkw // do as javac does for the suffix, prefer "new" to "$lessinit$greater$1"
case x => x.dropLocal
}
val newName = nme.ANON_FUN_NAME.append(nme.NAME_JOIN_STRING).append(ownerName)
mkMethodForFunctionBody(localTyper)(owner, fun, newName)(additionalFlags = ARTIFACT)
}
// the result type of a function or corresponding SAM type
private def functionResultType(tp: Type): Type = {
val dealiased = tp.dealiasWiden
if (isFunctionTypeDirect(dealiased)) dealiased.typeArgs.last
else samOf(tp) match {
case samSym if samSym.exists => tp.memberInfo(samSym).resultType.deconst
case _ => NoType
}
}
/**
* Lift a Function's body to a method. For use during Uncurry, where Function nodes have type FunctionN[T1, ..., Tn, R]
*
* It creates a method definition with value params derived from the original lambda
* or `methParamProtos` (used to create the correct override for sam methods).
*
* Replace the `fun.vparams` symbols by the newly created method params,
* changes owner of `fun.body` from `fun.symbol` to resulting method's symbol.
*
* @param owner The owner for the new method
* @param fun the function to take the body from
* @param name name for the new method
* @param additionalFlags flags to be put on the method in addition to FINAL
*/
private def mkMethodForFunctionBody(@unused localTyper: analyzer.Typer)
(owner: Symbol, fun: Function, name: TermName)
(methParamProtos: List[Symbol] = fun.vparams.map(_.symbol),
resTp: Type = functionResultType(fun.tpe),
additionalFlags: FlagSet = NoFlags): DefDef = {
val methSym = owner.newMethod(name, fun.pos, FINAL | additionalFlags)
// for sams, methParamProtos is the parameter symbols for the sam's method, so that we generate the correct override (based on parameter types)
val methParamSyms = methParamProtos.map { param => methSym.newSyntheticValueParam(param.tpe, param.name.toTermName) }
methSym setInfo MethodType(methParamSyms, resTp)
// we must rewire reference to the function's param symbols -- and not methParamProtos -- to methParamSyms
val useMethodParams = new TreeSymSubstituter(fun.vparams.map(_.symbol), methParamSyms)
// we're now owned by the method that holds the body, and not the function
val moveToMethod = new ChangeOwnerTraverser(fun.symbol, methSym)
def substThisForModule(tree: Tree) = {
// Rewrite This(enclModuleClass) to Ident(enclModuleClass) to avoid unnecessary capture of the module
// class, which might hamper serializability.
//
// Analogous to this special case in ExplicitOuter: https://github.com/scala/scala/blob/d2d33ddf8c/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala#L410-L412
// that understands that such references shouldn't give rise to outer params.
val enclosingStaticModules = owner.ownersIterator.filter(x => !x.hasPackageFlag && x.isModuleClass && x.isStatic)
enclosingStaticModules.foldLeft(tree)((tree, moduleClass) => tree.substituteThis(moduleClass, gen.mkAttributedIdent(moduleClass.sourceModule)) )
}
newDefDef(methSym, substThisForModule(moveToMethod(useMethodParams(fun.body))))(tpt = TypeTree(resTp))
}
/**
* Create a new `DefDef` based on `orig` with an explicit self parameter.
*
* Details:
* - Must by run after erasure
* - If `maybeClone` is the identity function, this runs "in place"
* and mutates the symbol of `orig`. `orig` should be discarded
* - Symbol owners and returns are substituted, as are parameter symbols
* - Recursive calls are not rewritten. This is correct if we assume
* that we either:
* - are in "in-place" mode, but can guarantee that no recursive calls exists
* - are associating the RHS with a cloned symbol, but intend for the original
* method to remain and for recursive calls to target it.
*/
final def mkStatic(orig: DefDef, newName: Name, maybeClone: Symbol => Symbol): DefDef = {
assert(phase.erasedTypes, phase)
assert(!orig.symbol.hasFlag(SYNCHRONIZED), orig.symbol.defString)
val origSym = orig.symbol
val origParams = orig.symbol.info.params
val newSym = maybeClone(orig.symbol)
newSym.setName(newName)
newSym.setFlag(STATIC)
// Add an explicit self parameter
val selfParamSym = newSym.newSyntheticValueParam(newSym.owner.typeConstructor, nme.SELF).setFlag(ARTIFACT)
newSym.updateInfo(newSym.info match {
case mt @ MethodType(params, res) => copyMethodType(mt, selfParamSym :: params, res)
case x => throw new MatchError(x)
})
val selfParam = ValDef(selfParamSym)
val rhs = orig.rhs.substituteThis(newSym.owner, gen.mkAttributedIdent(selfParamSym)) // scala/scala-dev#186 intentionally leaving Ident($this) is unpositioned
.substituteSymbols(origParams, newSym.info.params.drop(1)).changeOwner(origSym, newSym)
treeCopy.DefDef(orig, orig.mods, orig.name, orig.tparams, (selfParam :: orig.vparamss.head) :: Nil, orig.tpt, rhs).setSymbol(newSym)
}
def expandFunction(localTyper: analyzer.Typer)(fun: Function, inConstructorFlag: Long): Tree = {
val anonClass = fun.symbol.owner.newAnonymousFunctionClass(fun.pos, inConstructorFlag)
val typeSym = fun.tpe.typeSymbol
val isFunction = isFunctionSymbol(typeSym)
if (isFunction || typeSym.isSubClass(SerializableClass))
anonClass.addAnnotation(SerialVersionUIDAnnotation)
val rScope = newScope
def parents(tp: Type): List[Type] = tp match {
case RefinedType(ps, scope) =>
assert(scope.forall(_.isType), s"Cannot expand function of type $tp")
ps.flatMap(parents).tap(_ => scope.foreach(rScope.enter))
case _ =>
if (!isFunction) tp :: Nil
else addSerializable(abstractFunctionType(fun.vparams.map(_.symbol.tpe), fun.body.tpe.deconst))
}
anonClass.setInfo(ClassInfoType(parents(fun.tpe), rScope, anonClass))
// The original owner is used in the backend for the EnclosingMethod attribute. If fun is
// nested in a value-class method, its owner was already changed to the extension method.
// Saving the original owner allows getting the source structure from the class symbol.
defineOriginalOwner(anonClass, fun.symbol.originalOwner)
val samDef = mkMethodFromFunction(localTyper)(anonClass, fun)
anonClass.info.decls enter samDef.symbol
localTyper.typedPos(fun.pos) {
Block(
ClassDef(anonClass, NoMods, ListOfNil, List(samDef), fun.pos),
Typed(New(anonClass.tpe), TypeTree(fun.tpe)),
)
}
}
override def isPatVarWarnable = settings.warnUnusedPatVars
override def isVarDefWarnable = settings.lintValPatterns
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy