dotty.tools.dotc.ast.Desugar.scala Maven / Gradle / Ivy
The newest version!
package dotty.tools
package dotc
package ast
import core._
import util.Spans._, Types._, Contexts._, Constants._, Names._, NameOps._, Flags._
import Symbols._, StdNames._, Trees._
import Decorators._, transform.SymUtils._
import NameKinds.{UniqueName, EvidenceParamName, DefaultGetterName}
import typer.FrontEnd
import util.{Property, SourceFile, SourcePosition}
import util.NameTransformer.avoidIllegalChars
import collection.mutable.ListBuffer
import reporting.diagnostic.messages._
import reporting.trace
import annotation.constructorOnly
import printing.Formatting.hl
import config.Printers
import scala.annotation.internal.sharable
object desugar {
import untpd._
import DesugarEnums._
/** If a Select node carries this attachment, suppress the check
* that its type refers to an acessible symbol.
*/
val SuppressAccessCheck: Property.Key[Unit] = Property.Key()
/** An attachment for companion modules of classes that have a `derives` clause.
* The position value indicates the start position of the template of the
* deriving class.
*/
val DerivingCompanion: Property.Key[SourcePosition] = Property.Key()
/** An attachment for match expressions generated from a PatDef or GenFrom.
* Value of key == one of IrrefutablePatDef, IrrefutableGenFrom
*/
val CheckIrrefutable: Property.Key[MatchCheck] = Property.StickyKey()
/** What static check should be applied to a Match? */
enum MatchCheck {
case None, Exhaustive, IrrefutablePatDef, IrrefutableGenFrom
}
/** Info of a variable in a pattern: The named tree and its type */
private type VarInfo = (NameTree, Tree)
/** Is `name` the name of a method that can be invalidated as a compiler-generated
* case class method if it clashes with a user-defined method?
*/
def isRetractableCaseClassMethodName(name: Name)(implicit ctx: Context): Boolean = name match {
case nme.apply | nme.unapply | nme.unapplySeq | nme.copy => true
case DefaultGetterName(nme.copy, _) => true
case _ => false
}
/** Is `name` the name of a method that is added unconditionally to case classes? */
def isDesugaredCaseClassMethodName(name: Name)(implicit ctx: Context): Boolean =
isRetractableCaseClassMethodName(name) || name.isSelectorName
// ----- DerivedTypeTrees -----------------------------------
class SetterParamTree(implicit @constructorOnly src: SourceFile) extends DerivedTypeTree {
def derivedTree(sym: Symbol)(implicit ctx: Context): tpd.TypeTree = tpd.TypeTree(sym.info.resultType)
}
class TypeRefTree(implicit @constructorOnly src: SourceFile) extends DerivedTypeTree {
def derivedTree(sym: Symbol)(implicit ctx: Context): tpd.TypeTree = tpd.TypeTree(sym.typeRef)
}
class TermRefTree(implicit @constructorOnly src: SourceFile) extends DerivedTypeTree {
def derivedTree(sym: Symbol)(implicit ctx: Context): tpd.Tree = tpd.ref(sym)
}
/** A type tree that computes its type from an existing parameter.
* @param suffix String difference between existing parameter (call it `P`) and parameter owning the
* DerivedTypeTree (call it `O`). We have: `O.name == P.name + suffix`.
*/
class DerivedFromParamTree(suffix: String)(implicit @constructorOnly src: SourceFile) extends DerivedTypeTree {
/** Make sure that for all enclosing module classes their companion classes
* are completed. Reason: We need the constructor of such companion classes to
* be completed so that OriginalSymbol attachments are pushed to DerivedTypeTrees
* in apply/unapply methods.
*/
override def ensureCompletions(implicit ctx: Context): Unit =
if (!ctx.owner.is(Package))
if (ctx.owner.isClass) {
ctx.owner.ensureCompleted()
if (ctx.owner.is(ModuleClass))
ctx.owner.linkedClass.ensureCompleted()
}
else ensureCompletions(ctx.outer)
/** Return info of original symbol, where all references to siblings of the
* original symbol (i.e. sibling and original symbol have the same owner)
* are rewired to like-named* parameters or accessors in the scope enclosing
* the current scope. The current scope is the scope owned by the defined symbol
* itself, that's why we have to look one scope further out. If the resulting
* type is an alias type, dealias it. This is necessary because the
* accessor of a type parameter is a private type alias that cannot be accessed
* from subclasses.
*
* (*) like-named means:
*
* parameter name == reference name ++ suffix
*/
def derivedTree(sym: Symbol)(implicit ctx: Context): tpd.TypeTree = {
val relocate = new TypeMap {
val originalOwner = sym.owner
def apply(tp: Type) = tp match {
case tp: NamedType if tp.symbol.exists && (tp.symbol.owner eq originalOwner) =>
val defctx = ctx.outersIterator.dropWhile(_.scope eq ctx.scope).next()
var local = defctx.denotNamed(tp.name ++ suffix).suchThat(_.isParamOrAccessor).symbol
if (local.exists) (defctx.owner.thisType select local).dealiasKeepAnnots
else {
def msg =
s"no matching symbol for ${tp.symbol.showLocated} in ${defctx.owner} / ${defctx.effectiveScope.toList}"
ErrorType(msg).assertingErrorsReported(msg)
}
case _ =>
mapOver(tp)
}
}
tpd.TypeTree(relocate(sym.info))
}
}
/** A type definition copied from `tdef` with a rhs typetree derived from it */
def derivedTypeParam(tdef: TypeDef, suffix: String = "")(implicit ctx: Context): TypeDef =
cpy.TypeDef(tdef)(
name = tdef.name ++ suffix,
rhs = DerivedFromParamTree(suffix).withSpan(tdef.rhs.span).watching(tdef)
)
/** A derived type definition watching `sym` */
def derivedTypeParam(sym: TypeSymbol)(implicit ctx: Context): TypeDef =
TypeDef(sym.name, DerivedFromParamTree("").watching(sym)).withFlags(TypeParam)
/** A value definition copied from `vdef` with a tpt typetree derived from it */
def derivedTermParam(vdef: ValDef)(implicit ctx: Context): ValDef =
cpy.ValDef(vdef)(
tpt = DerivedFromParamTree("").withSpan(vdef.tpt.span).watching(vdef))
// ----- Desugar methods -------------------------------------------------
/** var x: Int = expr
* ==>
* def x: Int = expr
* def x_=($1: ): Unit = ()
*/
def valDef(vdef0: ValDef)(implicit ctx: Context): Tree = {
val vdef @ ValDef(name, tpt, rhs) = transformQuotedPatternName(vdef0)
val mods = vdef.mods
val setterNeeded =
mods.is(Mutable) && ctx.owner.isClass && (!mods.isAllOf(PrivateLocal) || ctx.owner.is(Trait))
if (setterNeeded) {
// TODO: copy of vdef as getter needed?
// val getter = ValDef(mods, name, tpt, rhs) withPos vdef.pos?
// right now vdef maps via expandedTree to a thicket which concerns itself.
// I don't see a problem with that but if there is one we can avoid it by making a copy here.
val setterParam = makeSyntheticParameter(tpt = SetterParamTree().watching(vdef))
// The rhs gets filled in later, when field is generated and getter has parameters (see Memoize miniphase)
val setterRhs = if (vdef.rhs.isEmpty) EmptyTree else unitLiteral
val setter = cpy.DefDef(vdef)(
name = name.setterName,
tparams = Nil,
vparamss = (setterParam :: Nil) :: Nil,
tpt = TypeTree(defn.UnitType),
rhs = setterRhs
).withMods((mods | Accessor) &~ (CaseAccessor | GivenOrImplicit | Lazy))
Thicket(vdef, setter)
}
else vdef
}
def makeImplicitParameters(tpts: List[Tree], implicitFlag: FlagSet, forPrimaryConstructor: Boolean = false)(implicit ctx: Context): List[ValDef] =
for (tpt <- tpts) yield {
val paramFlags: FlagSet = if (forPrimaryConstructor) LocalParamAccessor else Param
val epname = EvidenceParamName.fresh()
ValDef(epname, tpt, EmptyTree).withFlags(paramFlags | implicitFlag)
}
/** 1. Expand context bounds to evidence params. E.g.,
*
* def f[T >: L <: H : B](params)
* ==>
* def f[T >: L <: H](params)(implicit evidence$0: B[T])
*
* 2. Expand default arguments to default getters. E.g,
*
* def f[T: B](x: Int = 1)(y: String = x + "m") = ...
* ==>
* def f[T](x: Int)(y: String)(implicit evidence$0: B[T]) = ...
* def f$default$1[T] = 1
* def f$default$2[T](x: Int) = x + "m"
*
* 3. Convert <: T to : T in specializing inline methods. E.g.
*
* inline def f(x: Boolean) <: Any = if (x) 1 else ""
* ==>
* inline def f(x: Boolean): Any = if (x) 1 else ""
*
* 4. Upcast non-specializing inline methods. E.g.
*
* inline def f(x: Boolean): Any = if (x) 1 else ""
* ==>
* inline def f(x: Boolean): Any = (if (x) 1 else ""): Any
*/
private def defDef(meth0: DefDef, isPrimaryConstructor: Boolean = false)(implicit ctx: Context): Tree = {
val meth @ DefDef(_, tparams, vparamss, tpt, rhs) = transformQuotedPatternName(meth0)
val methName = normalizeName(meth, tpt).asTermName
val mods = meth.mods
val epbuf = ListBuffer[ValDef]()
def desugarContextBounds(rhs: Tree): Tree = rhs match {
case ContextBounds(tbounds, cxbounds) =>
epbuf ++= makeImplicitParameters(cxbounds, Implicit, forPrimaryConstructor = isPrimaryConstructor)
tbounds
case LambdaTypeTree(tparams, body) =>
cpy.LambdaTypeTree(rhs)(tparams, desugarContextBounds(body))
case _ =>
rhs
}
def dropContextBounds(tparam: TypeDef): TypeDef = {
def dropInRhs(rhs: Tree): Tree = rhs match {
case ContextBounds(tbounds, _) =>
tbounds
case rhs @ LambdaTypeTree(tparams, body) =>
cpy.LambdaTypeTree(rhs)(tparams, dropInRhs(body))
case _ =>
rhs
}
cpy.TypeDef(tparam)(rhs = dropInRhs(tparam.rhs))
}
val tparams1 = tparams mapConserve { tparam =>
cpy.TypeDef(tparam)(rhs = desugarContextBounds(tparam.rhs))
}
var meth1 = addEvidenceParams(
cpy.DefDef(meth)(name = methName, tparams = tparams1), epbuf.toList)
if (meth1.mods.is(Inline))
meth1.tpt match {
case TypeBoundsTree(_, tpt1) =>
meth1 = cpy.DefDef(meth1)(tpt = tpt1)
case tpt if !tpt.isEmpty && !meth1.rhs.isEmpty =>
meth1 = cpy.DefDef(meth1)(rhs = Typed(meth1.rhs, tpt))
case _ =>
}
/** The longest prefix of parameter lists in vparamss whose total length does not exceed `n` */
def takeUpTo(vparamss: List[List[ValDef]], n: Int): List[List[ValDef]] = vparamss match {
case vparams :: vparamss1 =>
val len = vparams.length
if (n >= len) vparams :: takeUpTo(vparamss1, n - len) else Nil
case _ =>
Nil
}
def normalizedVparamss = meth1.vparamss map (_ map (vparam =>
cpy.ValDef(vparam)(rhs = EmptyTree)))
def defaultGetters(vparamss: List[List[ValDef]], n: Int): List[DefDef] = vparamss match {
case (vparam :: vparams) :: vparamss1 =>
def defaultGetter: DefDef =
DefDef(
name = DefaultGetterName(methName, n),
tparams = meth.tparams.map(tparam => dropContextBounds(toDefParam(tparam))),
vparamss = takeUpTo(normalizedVparamss.nestedMap(toDefParam), n),
tpt = TypeTree(),
rhs = vparam.rhs
)
.withMods(Modifiers(mods.flags & (AccessFlags | Synthetic), mods.privateWithin))
val rest = defaultGetters(vparams :: vparamss1, n + 1)
if (vparam.rhs.isEmpty) rest else defaultGetter :: rest
case Nil :: vparamss1 =>
defaultGetters(vparamss1, n)
case nil =>
Nil
}
val defGetters = defaultGetters(vparamss, 0)
if (defGetters.isEmpty) meth1
else {
val meth2 = cpy.DefDef(meth1)(vparamss = normalizedVparamss)
.withMods(meth1.mods | DefaultParameterized)
Thicket(meth2 :: defGetters)
}
}
/** Transforms a definition with a name starting with a `$` in a quoted pattern into a `quoted.binding.Binding` splice.
*
* The desugaring consists in adding the `@patternBindHole` annotation. This annotation is used during typing to perform the full transformation.
*
* A definition
* ```scala
* case '{ def $a(...) = ...; ... `$a`() ... } => a
* ```
* into
* ```scala
* case '{ @patternBindHole def `$a`(...) = ...; ... `$a`() ... } => a
* ```
*/
def transformQuotedPatternName(tree: ValOrDefDef)(implicit ctx: Context): ValOrDefDef = {
if (ctx.mode.is(Mode.QuotedPattern) && !isBackquoted(tree) && tree.name != nme.ANON_FUN && tree.name.startsWith("$")) {
val mods = tree.mods.withAddedAnnotation(New(ref(defn.InternalQuoted_patternBindHoleAnnot.typeRef)).withSpan(tree.span))
tree.withMods(mods)
} else tree
}
/** Add an explicit ascription to the `expectedTpt` to every tail splice.
*
* - `'{ x }` -> `'{ x }`
* - `'{ $x }` -> `'{ $x: T }`
* - `'{ if (...) $x else $y }` -> `'{ if (...) ($x: T) else ($y: T) }`
*
* Note that the splice `$t: T` will be typed as `${t: Expr[T]}`
*/
def quotedPattern(tree: untpd.Tree, expectedTpt: untpd.Tree)(implicit ctx: Context): untpd.Tree = {
def adaptToExpectedTpt(tree: untpd.Tree): untpd.Tree = tree match {
// Add the expected type as an ascription
case _: untpd.Splice =>
untpd.Typed(tree, expectedTpt).withSpan(tree.span)
case Typed(expr: untpd.Splice, tpt) =>
cpy.Typed(tree)(expr, untpd.makeAndType(tpt, expectedTpt).withSpan(tpt.span))
// Propagate down the expected type to the leafs of the expression
case Block(stats, expr) =>
cpy.Block(tree)(stats, adaptToExpectedTpt(expr))
case If(cond, thenp, elsep) =>
cpy.If(tree)(cond, adaptToExpectedTpt(thenp), adaptToExpectedTpt(elsep))
case untpd.Parens(expr) =>
cpy.Parens(tree)(adaptToExpectedTpt(expr))
case Match(selector, cases) =>
val newCases = cases.map(cdef => cpy.CaseDef(cdef)(body = adaptToExpectedTpt(cdef.body)))
cpy.Match(tree)(selector, newCases)
case untpd.ParsedTry(expr, handler, finalizer) =>
cpy.ParsedTry(tree)(adaptToExpectedTpt(expr), adaptToExpectedTpt(handler), finalizer)
// Tree does not need to be ascribed
case _ =>
tree
}
adaptToExpectedTpt(tree)
}
// Add all evidence parameters in `params` as implicit parameters to `meth` */
private def addEvidenceParams(meth: DefDef, params: List[ValDef])(implicit ctx: Context): DefDef =
params match {
case Nil =>
meth
case evidenceParams =>
val vparamss1 = meth.vparamss.reverse match {
case (vparams @ (vparam :: _)) :: rvparamss if vparam.mods.isOneOf(GivenOrImplicit) =>
((evidenceParams ++ vparams) :: rvparamss).reverse
case _ =>
meth.vparamss :+ evidenceParams
}
cpy.DefDef(meth)(vparamss = vparamss1)
}
/** The implicit evidence parameters of `meth`, as generated by `desugar.defDef` */
private def evidenceParams(meth: DefDef)(implicit ctx: Context): List[ValDef] =
meth.vparamss.reverse match {
case (vparams @ (vparam :: _)) :: _ if vparam.mods.isOneOf(GivenOrImplicit) =>
vparams.dropWhile(!_.name.is(EvidenceParamName))
case _ =>
Nil
}
@sharable private val synthetic = Modifiers(Synthetic)
private def toDefParam(tparam: TypeDef): TypeDef =
tparam.withMods(tparam.rawMods & EmptyFlags | Param)
private def toDefParam(vparam: ValDef): ValDef =
vparam.withMods(vparam.rawMods & (GivenOrImplicit | Erased) | Param)
/** The expansion of a class definition. See inline comments for what is involved */
def classDef(cdef: TypeDef)(implicit ctx: Context): Tree = {
val impl @ Template(constr0, _, self, _) = cdef.rhs
val className = normalizeName(cdef, impl).asTypeName
val parents = impl.parents
val mods = cdef.mods
val companionMods = mods
.withFlags((mods.flags & (AccessFlags | Final)).toCommonFlags)
.withMods(Nil)
var defaultGetters: List[Tree] = Nil
def decompose(ddef: Tree): DefDef = ddef match {
case meth: DefDef => meth
case Thicket((meth: DefDef) :: defaults) =>
defaultGetters = defaults
meth
}
val constr1 = decompose(defDef(impl.constr, isPrimaryConstructor = true))
// The original type and value parameters in the constructor already have the flags
// needed to be type members (i.e. param, and possibly also private and local unless
// prefixed by type or val). `tparams` and `vparamss` are the type parameters that
// go in `constr`, the constructor after desugaring.
/** Does `tree' look like a reference to AnyVal? Temporary test before we have inline classes */
def isAnyVal(tree: Tree): Boolean = tree match {
case Ident(tpnme.AnyVal) => true
case Select(qual, tpnme.AnyVal) => isScala(qual)
case _ => false
}
def isScala(tree: Tree): Boolean = tree match {
case Ident(nme.scala_) => true
case Select(Ident(nme.ROOTPKG), nme.scala_) => true
case _ => false
}
def namePos = cdef.sourcePos.withSpan(cdef.nameSpan)
val isObject = mods.is(Module)
val isCaseClass = mods.is(Case) && !isObject
val isCaseObject = mods.is(Case) && isObject
val isEnum = mods.isEnumClass && !mods.is(Module)
def isEnumCase = mods.isEnumCase
val isValueClass = parents.nonEmpty && isAnyVal(parents.head)
// This is not watertight, but `extends AnyVal` will be replaced by `inline` later.
val originalTparams = constr1.tparams
val originalVparamss = constr1.vparamss
lazy val derivedEnumParams = enumClass.typeParams.map(derivedTypeParam)
val impliedTparams =
if (isEnumCase) {
val tparamReferenced = typeParamIsReferenced(
enumClass.typeParams, originalTparams, originalVparamss, parents)
if (originalTparams.isEmpty && (parents.isEmpty || tparamReferenced))
derivedEnumParams.map(tdef => tdef.withFlags(tdef.mods.flags | PrivateLocal))
else originalTparams
}
else originalTparams
val constrTparams = impliedTparams.map(toDefParam)
val constrVparamss =
if (originalVparamss.isEmpty) { // ensure parameter list is non-empty
if (isCaseClass && originalTparams.isEmpty)
ctx.error(CaseClassMissingParamList(cdef), namePos)
ListOfNil
} else if (isCaseClass && originalVparamss.head.exists(_.mods.isOneOf(GivenOrImplicit))) {
ctx.error("Case classes should have a non-implicit parameter list", namePos)
ListOfNil
}
else originalVparamss.nestedMap(toDefParam)
val constr = cpy.DefDef(constr1)(tparams = constrTparams, vparamss = constrVparamss)
val (normalizedBody, enumCases, enumCompanionRef) = {
// Add constructor type parameters and evidence implicit parameters
// to auxiliary constructors; set defaultGetters as a side effect.
def expandConstructor(tree: Tree) = tree match {
case ddef: DefDef if ddef.name.isConstructorName =>
decompose(
defDef(
addEvidenceParams(
cpy.DefDef(ddef)(tparams = constrTparams),
evidenceParams(constr1).map(toDefParam))))
case stat =>
stat
}
// The Identifiers defined by a case
def caseIds(tree: Tree) = tree match {
case tree: MemberDef => Ident(tree.name.toTermName) :: Nil
case PatDef(_, ids, _, _) => ids
}
val stats = impl.body.map(expandConstructor)
if (isEnum) {
val (enumCases, enumStats) = stats.partition(DesugarEnums.isEnumCase)
if (enumCases.isEmpty)
ctx.error("Enumerations must constain at least one case", namePos)
val enumCompanionRef = TermRefTree()
val enumImport = Import(importDelegate = false, enumCompanionRef, enumCases.flatMap(caseIds))
(enumImport :: enumStats, enumCases, enumCompanionRef)
}
else (stats, Nil, EmptyTree)
}
def anyRef = ref(defn.AnyRefAlias.typeRef)
val derivedTparams = constrTparams.map(derivedTypeParam(_))
val derivedVparamss = constrVparamss.nestedMap(derivedTermParam(_))
val arity = constrVparamss.head.length
val classTycon: Tree = TypeRefTree() // watching is set at end of method
def appliedTypeTree(tycon: Tree, args: List[Tree]) =
(if (args.isEmpty) tycon else AppliedTypeTree(tycon, args))
.withSpan(cdef.span.startPos)
def isHK(tparam: Tree): Boolean = tparam match {
case TypeDef(_, LambdaTypeTree(tparams, body)) => true
case TypeDef(_, rhs: DerivedTypeTree) => isHK(rhs.watched)
case _ => false
}
def appliedRef(tycon: Tree, tparams: List[TypeDef] = constrTparams, widenHK: Boolean = false) = {
val targs = for (tparam <- tparams) yield {
val targ = refOfDef(tparam)
def fullyApplied(tparam: Tree): Tree = tparam match {
case TypeDef(_, LambdaTypeTree(tparams, body)) =>
AppliedTypeTree(targ, tparams.map(_ => TypeBoundsTree(EmptyTree, EmptyTree)))
case TypeDef(_, rhs: DerivedTypeTree) =>
fullyApplied(rhs.watched)
case _ =>
targ
}
if (widenHK) fullyApplied(tparam) else targ
}
appliedTypeTree(tycon, targs)
}
def isRepeated(tree: Tree): Boolean = tree match {
case PostfixOp(_, Ident(tpnme.raw.STAR)) => true
case ByNameTypeTree(tree1) => isRepeated(tree1)
case _ => false
}
// a reference to the class type bound by `cdef`, with type parameters coming from the constructor
val classTypeRef = appliedRef(classTycon)
// a reference to `enumClass`, with type parameters coming from the case constructor
lazy val enumClassTypeRef =
if (enumClass.typeParams.isEmpty)
enumClassRef
else if (originalTparams.isEmpty)
appliedRef(enumClassRef)
else {
ctx.error(i"explicit extends clause needed because both enum case and enum class have type parameters"
, cdef.sourcePos.startPos)
appliedTypeTree(enumClassRef, constrTparams map (_ => anyRef))
}
// new C[Ts](paramss)
lazy val creatorExpr = {
val vparamss = constrVparamss match {
case (vparam :: _) :: _ if vparam.mods.isOneOf(GivenOrImplicit) => // add a leading () to match class parameters
Nil :: constrVparamss
case _ =>
constrVparamss
}
val nu = (makeNew(classTypeRef) /: vparamss) { (nu, vparams) =>
val app = Apply(nu, vparams.map(refOfDef))
vparams match {
case vparam :: _ if vparam.mods.is(Given) => app.setGivenApply()
case _ => app
}
}
ensureApplied(nu)
}
val copiedAccessFlags = if (ctx.scala2Setting) EmptyFlags else AccessFlags
// Methods to add to a case class C[..](p1: T1, ..., pN: Tn)(moreParams)
// def _1: T1 = this.p1
// ...
// def _N: TN = this.pN
// def copy(p1: T1 = p1: @uncheckedVariance, ...,
// pN: TN = pN: @uncheckedVariance)(moreParams) =
// new C[...](p1, ..., pN)(moreParams)
//
// Note: copy default parameters need @uncheckedVariance; see
// neg/t1843-variances.scala for a test case. The test would give
// two errors without @uncheckedVariance, one of them spurious.
val caseClassMeths = {
def syntheticProperty(name: TermName, tpt: Tree, rhs: Tree) =
DefDef(name, Nil, Nil, tpt, rhs).withMods(synthetic)
def productElemMeths = {
val caseParams = derivedVparamss.head.toArray
for (i <- List.range(0, arity) if nme.selectorName(i) `ne` caseParams(i).name)
yield syntheticProperty(nme.selectorName(i), caseParams(i).tpt,
Select(This(EmptyTypeIdent), caseParams(i).name))
}
def ordinalMeths = if (isEnumCase) ordinalMethLit(nextOrdinal(CaseKind.Class)._1) :: Nil else Nil
def copyMeths = {
val hasRepeatedParam = constrVparamss.exists(_.exists {
case ValDef(_, tpt, _) => isRepeated(tpt)
})
if (mods.is(Abstract) || hasRepeatedParam) Nil // cannot have default arguments for repeated parameters, hence copy method is not issued
else {
def copyDefault(vparam: ValDef) =
makeAnnotated("scala.annotation.unchecked.uncheckedVariance", refOfDef(vparam))
val copyFirstParams = derivedVparamss.head.map(vparam =>
cpy.ValDef(vparam)(rhs = copyDefault(vparam)))
val copyRestParamss = derivedVparamss.tail.nestedMap(vparam =>
cpy.ValDef(vparam)(rhs = EmptyTree))
DefDef(nme.copy, derivedTparams, copyFirstParams :: copyRestParamss, TypeTree(), creatorExpr)
.withMods(Modifiers(Synthetic | constr1.mods.flags & copiedAccessFlags, constr1.mods.privateWithin)) :: Nil
}
}
// TODO When the Scala library is updated to 2.13.x add the override keyword to this generated method.
// (because Product.scala was updated)
def productElemNameMeth = {
val methodParam = makeSyntheticParameter(tpt = scalaDot(tpnme.Int))
val paramRef = Ident(methodParam.name)
val indexAsString = Apply(Select(javaDotLangDot(nme.String), nme.valueOf), paramRef)
val throwOutOfBound = Throw(New(javaDotLangDot(tpnme.IOOBException), List(List(indexAsString))))
val defaultCase = CaseDef(Ident(nme.WILDCARD), EmptyTree, throwOutOfBound)
val patternMatchCases = derivedVparamss.head.zipWithIndex.map { case (param, idx) =>
CaseDef(Literal(Constant(idx)), EmptyTree, Literal(Constant(param.name.decode.toString)))
} :+ defaultCase
val body = Match(paramRef, patternMatchCases)
DefDef(nme.productElementName, Nil, List(List(methodParam)), javaDotLangDot(tpnme.String), body)
.withFlags(if (defn.isNewCollections) Override | Synthetic else Synthetic)
}
if (isCaseClass)
productElemNameMeth :: copyMeths ::: ordinalMeths ::: productElemMeths
else Nil
}
var parents1 = parents
if (isEnumCase && parents.isEmpty)
parents1 = enumClassTypeRef :: Nil
if (isCaseClass | isCaseObject)
parents1 = parents1 :+ scalaDot(str.Product.toTypeName) :+ scalaDot(nme.Serializable.toTypeName)
else if (isObject)
parents1 = parents1 :+ scalaDot(nme.Serializable.toTypeName)
if (isEnum)
parents1 = parents1 :+ ref(defn.EnumType)
// derived type classes of non-module classes go to their companions
val (clsDerived, companionDerived) =
if (mods.is(Module)) (impl.derived, Nil) else (Nil, impl.derived)
// The thicket which is the desugared version of the companion object
// synthetic object C extends parentTpt derives class-derived { defs }
def companionDefs(parentTpt: Tree, defs: List[Tree]) = {
val mdefs = moduleDef(
ModuleDef(
className.toTermName, Template(emptyConstructor, parentTpt :: Nil, companionDerived, EmptyValDef, defs))
.withMods(companionMods | Synthetic))
.withSpan(cdef.span).toList
if (companionDerived.nonEmpty)
for (modClsDef @ TypeDef(_, _) <- mdefs)
modClsDef.putAttachment(DerivingCompanion, impl.sourcePos.startPos)
mdefs
}
val companionMembers = defaultGetters ::: enumCases
// The companion object definitions, if a companion is needed, Nil otherwise.
// companion definitions include:
// 1. If class is a case class case class C[Ts](p1: T1, ..., pN: TN)(moreParams):
// def apply[Ts](p1: T1, ..., pN: TN)(moreParams) = new C[Ts](p1, ..., pN)(moreParams) (unless C is abstract)
// def unapply[Ts]($1: C[Ts]) = $1 // if not repeated
// def unapplySeq[Ts]($1: C[Ts]) = $1 // if repeated
// 2. The default getters of the constructor
// The parent of the companion object of a non-parameterized case class
// (T11, ..., T1N) => ... => (TM1, ..., TMN) => C
// For all other classes, the parent is AnyRef.
val companions =
if (isCaseClass) {
// The return type of the `apply` method, and an (empty or singleton) list
// of widening coercions
val (applyResultTpt, widenDefs) =
if (!isEnumCase)
(TypeTree(), Nil)
else if (parents.isEmpty || enumClass.typeParams.isEmpty)
(enumClassTypeRef, Nil)
else
enumApplyResult(cdef, parents, derivedEnumParams, appliedRef(enumClassRef, derivedEnumParams))
// true if access to the apply method has to be restricted
// i.e. if the case class constructor is either private or qualified private
def restrictedAccess = {
val mods = constr1.mods
mods.is(Private) || (!mods.is(Protected) && mods.hasPrivateWithin)
}
val companionParent =
if (constrTparams.nonEmpty ||
constrVparamss.length > 1 ||
mods.is(Abstract) ||
restrictedAccess ||
isEnumCase) anyRef
else
// todo: also use anyRef if constructor has a dependent method type (or rule that out)!
(constrVparamss :\ classTypeRef) (
(vparams, restpe) => Function(vparams map (_.tpt), restpe))
def widenedCreatorExpr =
(creatorExpr /: widenDefs)((rhs, meth) => Apply(Ident(meth.name), rhs :: Nil))
val applyMeths =
if (mods.is(Abstract)) Nil
else {
val copiedFlagsMask = DefaultParameterized | (copiedAccessFlags & Private)
val appMods = {
val mods = Modifiers(Synthetic | constr1.mods.flags & copiedFlagsMask)
if (restrictedAccess) mods.withPrivateWithin(constr1.mods.privateWithin)
else mods
}
val app = DefDef(nme.apply, derivedTparams, derivedVparamss, applyResultTpt, widenedCreatorExpr)
.withMods(appMods)
app :: widenDefs
}
val unapplyMeth = {
val hasRepeatedParam = constrVparamss.head.exists {
case ValDef(_, tpt, _) => isRepeated(tpt)
}
val methName = if (hasRepeatedParam) nme.unapplySeq else nme.unapply
val unapplyParam = makeSyntheticParameter(tpt = classTypeRef)
val unapplyRHS = if (arity == 0) Literal(Constant(true)) else Ident(unapplyParam.name)
DefDef(methName, derivedTparams, (unapplyParam :: Nil) :: Nil, TypeTree(), unapplyRHS)
.withMods(synthetic)
}
companionDefs(companionParent, applyMeths ::: unapplyMeth :: companionMembers)
}
else if (companionMembers.nonEmpty || companionDerived.nonEmpty || isEnum)
companionDefs(anyRef, companionMembers)
else if (isValueClass) {
impl.constr.vparamss match {
case (_ :: Nil) :: _ => companionDefs(anyRef, Nil)
case _ => Nil // error will be emitted in typer
}
}
else Nil
enumCompanionRef match {
case ref: TermRefTree => // have the enum import watch the companion object
val (modVal: ValDef) :: _ = companions
ref.watching(modVal)
case _ =>
}
// For an implicit class C[Ts](p11: T11, ..., p1N: T1N) ... (pM1: TM1, .., pMN: TMN), the method
// synthetic implicit C[Ts](p11: T11, ..., p1N: T1N) ... (pM1: TM1, ..., pMN: TMN): C[Ts] =
// new C[Ts](p11, ..., p1N) ... (pM1, ..., pMN) =
val implicitWrappers =
if (!mods.isOneOf(DelegateOrImplicit))
Nil
else if (ctx.owner.is(Package)) {
ctx.error(TopLevelImplicitClass(cdef), cdef.sourcePos)
Nil
}
else if (mods.is(Trait)) {
ctx.error(TypesAndTraitsCantBeImplicit(), cdef.sourcePos)
Nil
}
else if (isCaseClass) {
ctx.error(ImplicitCaseClass(cdef), cdef.sourcePos)
Nil
}
else if (arity != 1 && !mods.is(Delegate)) {
ctx.error(ImplicitClassPrimaryConstructorArity(), cdef.sourcePos)
Nil
}
else {
val defParamss = constrVparamss match {
case Nil :: paramss =>
paramss // drop leading () that got inserted by class
// TODO: drop this once we do not silently insert empty class parameters anymore
case paramss => paramss
}
// implicit wrapper is typechecked in same scope as constructor, so
// we can reuse the constructor parameters; no derived params are needed.
DefDef(className.toTermName, constrTparams, defParamss, classTypeRef, creatorExpr)
.withMods(companionMods | mods.flags.toTermFlags & DelegateOrImplicit | Synthetic | Final)
.withSpan(cdef.span) :: Nil
}
val self1 = {
val selfType = if (self.tpt.isEmpty) classTypeRef else self.tpt
if (self.isEmpty) self
else cpy.ValDef(self)(tpt = selfType).withMods(self.mods | SelfName)
}
val cdef1 = addEnumFlags {
val originalTparamsIt = impliedTparams.toIterator
val originalVparamsIt = originalVparamss.toIterator.flatten
val tparamAccessors = derivedTparams.map(_.withMods(originalTparamsIt.next().mods))
val caseAccessor = if (isCaseClass) CaseAccessor else EmptyFlags
val vparamAccessors = derivedVparamss match {
case first :: rest =>
first.map(_.withMods(originalVparamsIt.next().mods | caseAccessor)) ++
rest.flatten.map(_.withMods(originalVparamsIt.next().mods))
case _ =>
Nil
}
cpy.TypeDef(cdef: TypeDef)(
name = className,
rhs = cpy.Template(impl)(constr, parents1, clsDerived, self1,
tparamAccessors ::: vparamAccessors ::: normalizedBody ::: caseClassMeths)): TypeDef
}
// install the watch on classTycon
classTycon match {
case tycon: DerivedTypeTree => tycon.watching(cdef1)
case _ =>
}
flatTree(cdef1 :: companions ::: implicitWrappers)
}.reporting(i"desugared: $result", Printers.desugar)
/** Expand
*
* object name extends parents { self => body }
*
* to:
* val name: name$ = New(name$)
* final class name$ extends parents { self: name.type => body }
*/
def moduleDef(mdef: ModuleDef)(implicit ctx: Context): Tree = {
val impl = mdef.impl
val mods = mdef.mods
val moduleName = normalizeName(mdef, impl).asTermName
def isEnumCase = mods.isEnumCase
def flagSourcePos(flag: FlagSet) = mods.mods.find(_.flags == flag).fold(mdef.sourcePos)(_.sourcePos)
if (mods.is(Abstract))
ctx.error(em"${hl("abstract")} modifier cannot be used for objects", flagSourcePos(Abstract))
if (mods.is(Sealed))
ctx.error(em"${hl("sealed")} modifier is redundant for objects", flagSourcePos(Sealed))
// Maybe this should be an error; see https://github.com/scala/bug/issues/11094.
if (mods.is(Final) && !mods.is(Synthetic))
ctx.warning(em"${hl("final")} modifier is redundant for objects", flagSourcePos(Final))
if (mods.is(Package))
PackageDef(Ident(moduleName), cpy.ModuleDef(mdef)(nme.PACKAGE, impl).withMods(mods &~ Package) :: Nil)
else if (isEnumCase) {
typeParamIsReferenced(enumClass.typeParams, Nil, Nil, impl.parents)
// used to check there are no illegal references to enum's type parameters in parents
expandEnumModule(moduleName, impl, mods, mdef.span)
}
else {
val clsName = moduleName.moduleClassName
val clsRef = Ident(clsName)
val modul = ValDef(moduleName, clsRef, New(clsRef, Nil))
.withMods(mods.toTermFlags & RetainedModuleValFlags | ModuleValCreationFlags)
.withSpan(mdef.span.startPos)
val ValDef(selfName, selfTpt, _) = impl.self
val selfMods = impl.self.mods
if (!selfTpt.isEmpty) ctx.error(ObjectMayNotHaveSelfType(mdef), impl.self.sourcePos)
val clsSelf = ValDef(selfName, SingletonTypeTree(Ident(moduleName)), impl.self.rhs)
.withMods(selfMods)
.withSpan(impl.self.span.orElse(impl.span.startPos))
val clsTmpl = cpy.Template(impl)(self = clsSelf, body = impl.body)
val cls = TypeDef(clsName, clsTmpl)
.withMods(mods.toTypeFlags & RetainedModuleClassFlags | ModuleClassCreationFlags)
Thicket(modul, classDef(cls).withSpan(mdef.span))
}
}
/** Transforms
*
* type $T >: Low <: Hi
*
* to
*
* @patternBindHole type $T >: Low <: Hi
*
* if the type is a type splice.
*/
def quotedPatternTypeDef(tree: TypeDef)(implicit ctx: Context): TypeDef = {
assert(ctx.mode.is(Mode.QuotedPattern))
if (tree.name.startsWith("$") && !tree.isBackquoted) {
val patternBindHoleAnnot = New(ref(defn.InternalQuoted_patternBindHoleAnnot.typeRef)).withSpan(tree.span)
val mods = tree.mods.withAddedAnnotation(patternBindHoleAnnot)
tree.withMods(mods)
} else tree
}
/** The normalized name of `mdef`. This means
* 1. Check that the name does not redefine a Scala core class.
* If it does redefine, issue an error and return a mangled name instead of the original one.
* 2. If the name is missing (this can be the case for instance definitions), invent one instead.
*/
def normalizeName(mdef: MemberDef, impl: Tree)(implicit ctx: Context): Name = {
var name = mdef.name
if (name.isEmpty) name = name.likeSpaced(s"${inventName(impl)}_given".toTermName)
if (ctx.owner == defn.ScalaPackageClass && defn.reservedScalaClassNames.contains(name.toTypeName)) {
def kind = if (name.isTypeName) "class" else "object"
ctx.error(em"illegal redefinition of standard $kind $name", mdef.sourcePos)
name = name.errorName
}
name
}
/** Invent a name for an anonymous instance with template `impl`.
*/
private def inventName(impl: Tree)(implicit ctx: Context): String = impl match {
case impl: Template =>
if (impl.parents.isEmpty)
impl.body.find {
case dd: DefDef if dd.mods.is(Extension) => true
case _ => false
} match {
case Some(DefDef(name, _, (vparam :: _) :: _, _, _)) =>
s"${name}_of_${inventTypeName(vparam.tpt)}"
case _ =>
ctx.error(i"anonymous instance must have `for` part or must define at least one extension method", impl.sourcePos)
nme.ERROR.toString
}
else
impl.parents.map(inventTypeName(_)).mkString("_")
case impl: Tree =>
inventTypeName(impl)
}
private class NameExtractor(followArgs: Boolean) extends UntypedTreeAccumulator[String] {
private def extractArgs(args: List[Tree])(implicit ctx: Context): String =
args.map(argNameExtractor.apply("", _)).mkString("_")
override def apply(x: String, tree: Tree)(implicit ctx: Context): String =
if (x.isEmpty)
tree match {
case Select(pre, nme.CONSTRUCTOR) => foldOver(x, pre)
case tree: RefTree if tree.name.isTypeName => tree.name.toString
case tree: TypeDef => tree.name.toString
case tree: AppliedTypeTree if followArgs && tree.args.nonEmpty =>
s"${apply(x, tree.tpt)}_${extractArgs(tree.args)}"
case tree: LambdaTypeTree =>
apply(x, tree.body)
case tree: Tuple =>
if (followArgs) extractArgs(tree.trees) else "Tuple"
case tree: Function if tree.args.nonEmpty =>
if (followArgs) s"${extractArgs(tree.args)}_to_${apply("", tree.body)}" else "Function"
case _ => foldOver(x, tree)
}
else x
}
private val typeNameExtractor = NameExtractor(followArgs = true)
private val argNameExtractor = NameExtractor(followArgs = false)
private def inventTypeName(tree: Tree)(implicit ctx: Context): String = typeNameExtractor("", tree)
/** val p1, ..., pN: T = E
* ==>
* makePatDef[[val p1: T1 = E]]; ...; makePatDef[[val pN: TN = E]]
*
* case e1, ..., eN
* ==>
* expandSimpleEnumCase([case e1]); ...; expandSimpleEnumCase([case eN])
*/
def patDef(pdef: PatDef)(implicit ctx: Context): Tree = flatTree {
val PatDef(mods, pats, tpt, rhs) = pdef
if (mods.isEnumCase)
pats map {
case id: Ident =>
expandSimpleEnumCase(id.name.asTermName, mods,
Span(id.span.start, id.span.end, id.span.start))
}
else {
val pats1 = if (tpt.isEmpty) pats else pats map (Typed(_, tpt))
pats1 map (makePatDef(pdef, mods, _, rhs))
}
}
/** The selector of a match, which depends of the given `checkMode`.
* @param sel the original selector
* @return if `checkMode` is
* - None : sel @unchecked
* - Exhaustive : sel
* - IrrefutablePatDef,
* IrrefutableGenFrom: sel @unchecked with attachment `CheckIrrefutable -> checkMode`
*/
def makeSelector(sel: Tree, checkMode: MatchCheck)(implicit ctx: Context): Tree =
if (checkMode == MatchCheck.Exhaustive) sel
else {
val sel1 = Annotated(sel, New(ref(defn.UncheckedAnnotType)))
if (checkMode != MatchCheck.None) sel1.pushAttachment(CheckIrrefutable, checkMode)
sel1
}
/** If `pat` is a variable pattern,
*
* val/var/lazy val p = e
*
* Otherwise, in case there is exactly one variable x_1 in pattern
* val/var/lazy val p = e ==> val/var/lazy val x_1 = (e: @unchecked) match (case p => (x_1))
*
* in case there are zero or more than one variables in pattern
* val/var/lazy p = e ==> private[this] synthetic [lazy] val t$ = (e: @unchecked) match (case p => (x_1, ..., x_N))
* val/var/def x_1 = t$._1
* ...
* val/var/def x_N = t$._N
* If the original pattern variable carries a type annotation, so does the corresponding
* ValDef or DefDef.
*/
def makePatDef(original: Tree, mods: Modifiers, pat: Tree, rhs: Tree)(implicit ctx: Context): Tree = pat match {
case IdPattern(named, tpt) =>
derivedValDef(original, named, tpt, rhs, mods)
case _ =>
def isTuplePattern(arity: Int): Boolean = pat match {
case Tuple(pats) if pats.size == arity =>
pats.forall(isVarPattern)
case _ => false
}
val isMatchingTuple: Tree => Boolean = {
case Tuple(es) => isTuplePattern(es.length)
case _ => false
}
// We can only optimize `val pat = if (...) e1 else e2` if:
// - `e1` and `e2` are both tuples of arity N
// - `pat` is a tuple of N variables or wildcard patterns like `(x1, x2, ..., xN)`
val tupleOptimizable = forallResults(rhs, isMatchingTuple)
val vars =
if (tupleOptimizable) // include `_`
pat match {
case Tuple(pats) =>
pats.map { case id: Ident => id -> TypeTree() }
}
else getVariables(pat) // no `_`
val ids = for ((named, _) <- vars) yield Ident(named.name)
val caseDef = CaseDef(pat, EmptyTree, makeTuple(ids))
val matchExpr =
if (tupleOptimizable) rhs
else Match(makeSelector(rhs, MatchCheck.IrrefutablePatDef), caseDef :: Nil)
vars match {
case Nil =>
matchExpr
case (named, tpt) :: Nil =>
derivedValDef(original, named, tpt, matchExpr, mods)
case _ =>
val tmpName = UniqueName.fresh()
val patMods =
mods & Lazy | Synthetic | (if (ctx.owner.isClass) PrivateLocal else EmptyFlags)
val firstDef =
ValDef(tmpName, TypeTree(), matchExpr)
.withSpan(pat.span.union(rhs.span)).withMods(patMods)
def selector(n: Int) = Select(Ident(tmpName), nme.selectorName(n))
val restDefs =
for (((named, tpt), n) <- vars.zipWithIndex if named.name != nme.WILDCARD)
yield
if (mods.is(Lazy)) derivedDefDef(original, named, tpt, selector(n), mods &~ Lazy)
else derivedValDef(original, named, tpt, selector(n), mods)
flatTree(firstDef :: restDefs)
}
}
/** Expand variable identifier x to x @ _ */
def patternVar(tree: Tree)(implicit ctx: Context): Bind = {
val Ident(name) = tree
Bind(name, Ident(nme.WILDCARD)).withSpan(tree.span)
}
/** The type of tests that check whether a MemberDef is OK for some flag.
* The test succeeds if the partial function is defined and returns true.
*/
type MemberDefTest = PartialFunction[MemberDef, Boolean]
val legalOpaque: MemberDefTest = {
case TypeDef(_, rhs) =>
def rhsOK(tree: Tree): Boolean = tree match {
case _: TypeBoundsTree | _: Template => false
case LambdaTypeTree(_, body) => rhsOK(body)
case _ => true
}
rhsOK(rhs)
}
/** Check that modifiers are legal for the definition `tree`.
* Right now, we only check for `opaque`. TODO: Move other modifier checks here.
*/
def checkModifiers(tree: Tree)(implicit ctx: Context): Tree = tree match {
case tree: MemberDef =>
var tested: MemberDef = tree
def fail(msg: String) = ctx.error(msg, tree.sourcePos)
def checkApplicable(flag: Flag, test: MemberDefTest): Unit =
if (tested.mods.is(flag) && !test.applyOrElse(tree, (md: MemberDef) => false)) {
fail(i"modifier `${flag.flagsString}` is not allowed for this definition")
tested = tested.withMods(tested.mods.withoutFlags(flag))
}
checkApplicable(Opaque, legalOpaque)
tested
case _ =>
tree
}
def defTree(tree: Tree)(implicit ctx: Context): Tree =
checkModifiers(tree) match {
case tree: ValDef => valDef(tree)
case tree: TypeDef =>
if (tree.isClassDef) classDef(tree)
else if (ctx.mode.is(Mode.QuotedPattern)) quotedPatternTypeDef(tree)
else tree
case tree: DefDef =>
if (tree.name.isConstructorName) tree // was already handled by enclosing classDef
else defDef(tree)
case tree: ModuleDef => moduleDef(tree)
case tree: PatDef => patDef(tree)
}
/** { stats; }
* ==>
* { stats; () }
*/
def block(tree: Block)(implicit ctx: Context): Block = tree.expr match {
case EmptyTree =>
cpy.Block(tree)(tree.stats,
unitLiteral.withSpan(if (tree.stats.isEmpty) tree.span else tree.span.endPos))
case _ =>
tree
}
/** Translate infix operation expression
*
* l op r ==> l.op(r) if op is left-associative
* ==> r.op(l) if op is right-associative
*/
def binop(left: Tree, op: Ident, right: Tree)(implicit ctx: Context): Apply = {
def assignToNamedArg(arg: Tree) = arg match {
case Assign(Ident(name), rhs) => cpy.NamedArg(arg)(name, rhs)
case _ => arg
}
def makeOp(fn: Tree, arg: Tree, selectPos: Span) = {
val args: List[Tree] = arg match {
case Parens(arg) => assignToNamedArg(arg) :: Nil
case Tuple(args) => args.mapConserve(assignToNamedArg)
case _ => arg :: Nil
}
Apply(Select(fn, op.name).withSpan(selectPos), args)
}
if (isLeftAssoc(op.name))
makeOp(left, right, Span(left.span.start, op.span.end, op.span.start))
else
makeOp(right, left, Span(op.span.start, right.span.end))
}
/** Translate tuple expressions of arity <= 22
*
* () ==> ()
* (t) ==> t
* (t1, ..., tN) ==> TupleN(t1, ..., tN)
*/
def smallTuple(tree: Tuple)(implicit ctx: Context): Tree = {
val ts = tree.trees
val arity = ts.length
assert(arity <= Definitions.MaxTupleArity)
def tupleTypeRef = defn.TupleType(arity)
if (arity == 0)
if (ctx.mode is Mode.Type) TypeTree(defn.UnitType) else unitLiteral
else if (ctx.mode is Mode.Type) AppliedTypeTree(ref(tupleTypeRef), ts)
else Apply(ref(tupleTypeRef.classSymbol.companionModule.termRef), ts)
}
/** Group all definitions that can't be at the toplevel in
* an object named `
© 2015 - 2025 Weber Informatics LLC | Privacy Policy