dotty.tools.dotc.ast.Desugar.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 ast
import core.*
import util.Spans.*, Types.*, Contexts.*, Constants.*, Names.*, NameOps.*, Flags.*
import Symbols.*, StdNames.*, Trees.*, ContextOps.*
import Decorators.*
import Annotations.Annotation
import NameKinds.{UniqueName, ContextBoundParamName, ContextFunctionParamName, DefaultGetterName, WildcardParamName}
import typer.{Namer, Checking}
import util.{Property, SourceFile, SourcePosition, SrcPos, Chars}
import config.{Feature, Config}
import config.Feature.{sourceVersion, migrateTo3, enabled, betterForsEnabled}
import config.SourceVersion.*
import collection.mutable
import reporting.*
import annotation.constructorOnly
import printing.Formatting.hl
import config.Printers
import parsing.Parsers
import scala.annotation.internal.sharable
import scala.annotation.threadUnsafe
object desugar {
import untpd.*
import DesugarEnums.*
/** 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()
/** A multi-line infix operation with the infix operator starting a new line.
* Used for explaining potential errors.
*/
val MultiLineInfix: Property.Key[Unit] = Property.StickyKey()
/** An attachment key to indicate that a ValDef originated from parameter untupling.
*/
val UntupledParam: Property.Key[Unit] = Property.StickyKey()
/** An attachment key to indicate that a ValDef is an evidence parameter
* for a context bound.
*/
val ContextBoundParam: Property.Key[Unit] = Property.StickyKey()
/** Marks a poly fcuntion apply method, so that we can handle adding evidence parameters to them in a special way
*/
val PolyFunctionApply: Property.Key[Unit] = Property.StickyKey()
/** What static check should be applied to a Match? */
enum MatchCheck {
case None, Exhaustive, IrrefutablePatDef, IrrefutableGenFrom
}
/** 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)(using 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)(using Context): Boolean =
isRetractableCaseClassMethodName(name) || name.isSelectorName
// ----- DerivedTypeTrees -----------------------------------
class SetterParamTree(implicit @constructorOnly src: SourceFile) extends DerivedTypeTree {
def derivedTree(sym: Symbol)(using Context): tpd.TypeTree = tpd.TypeTree(sym.info.resultType)
}
class TypeRefTree(implicit @constructorOnly src: SourceFile) extends DerivedTypeTree {
def derivedTree(sym: Symbol)(using Context): tpd.TypeTree = tpd.TypeTree(sym.typeRef)
}
class TermRefTree(implicit @constructorOnly src: SourceFile) extends DerivedTypeTree {
def derivedTree(sym: Symbol)(using Context): tpd.Tree = tpd.ref(sym)
}
/** A type tree that computes its type from an existing parameter. */
class DerivedFromParamTree()(implicit @constructorOnly src: SourceFile) extends DerivedTypeTree {
/** Complete the appropriate constructors so that OriginalSymbol attachments are
* pushed to DerivedTypeTrees.
*/
override def ensureCompletions(using Context): Unit = {
def completeConstructor(sym: Symbol) =
sym.infoOrCompleter match {
case completer: Namer#ClassCompleter if !sym.isCompleting =>
// An example, derived from tests/run/t6385.scala
//
// class Test():
// def t1: Foo = Foo(1)
// final case class Foo(value: Int)
//
// Here's the sequence of events:
// * The symbol for Foo.apply is forced to complete
// * The symbol for the `value` parameter of the apply method is forced to complete
// * Completing that value parameter requires typing its type, which is a DerivedTypeTrees,
// which only types if it has an OriginalSymbol.
// * So if the case class hasn't been completed, we need (at least) its constructor to be completed
//
// Test tests/neg/i9294.scala is an example of why isCompleting is necessary.
// Annotations are added while completing the constructor,
// so the back reference to foo reaches here which re-initiates the constructor completion.
// So we just skip, as completion is already being triggered.
completer.completeConstructor(sym)
case _ =>
}
if (!ctx.owner.is(Package))
if (ctx.owner.isClass) {
completeConstructor(ctx.owner)
if (ctx.owner.is(ModuleClass))
completeConstructor(ctx.owner.linkedClass)
}
else ensureCompletions(using 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 same-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.
*/
def derivedTree(sym: Symbol)(using 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 = mapCtx.outersIterator.dropWhile(_.scope eq mapCtx.scope).next()
var local = defctx.denotNamed(tp.name).suchThat(_.isParamOrAccessor).symbol
if (local.exists) (defctx.owner.thisType select local).dealiasKeepAnnots
else {
def msg =
em"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)(using Context): TypeDef =
cpy.TypeDef(tdef)(
rhs = DerivedFromParamTree().withSpan(tdef.rhs.span).watching(tdef)
)
/** A derived type definition watching `sym` */
def derivedTypeParamWithVariance(sym: TypeSymbol)(using Context): TypeDef =
val variance = VarianceFlags & sym.flags
TypeDef(sym.name, DerivedFromParamTree().watching(sym)).withFlags(TypeParam | Synthetic | variance)
/** A value definition copied from `vdef` with a tpt typetree derived from it */
def derivedTermParam(vdef: ValDef)(using Context): ValDef =
derivedTermParam(vdef, vdef.unforcedRhs)
def derivedTermParam(vdef: ValDef, rhs: LazyTree)(using Context): ValDef =
cpy.ValDef(vdef)(
tpt = DerivedFromParamTree().withSpan(vdef.tpt.span).watching(vdef),
rhs = rhs
)
// ----- Desugar methods -------------------------------------------------
/** Setter generation is needed for:
* - non-private class members
* - all trait members
* - all package object members
*/
def isSetterNeeded(valDef: ValDef)(using Context): Boolean = {
val mods = valDef.mods
mods.is(Mutable)
&& ctx.owner.isClass
&& (!mods.is(Private) || ctx.owner.is(Trait) || ctx.owner.isPackageObject)
}
/** var x: Int = expr
* ==>
* def x: Int = expr
* def x_=($1: ): Unit = ()
*
* Generate setter where needed
*/
def valDef(vdef0: ValDef)(using Context): Tree =
val vdef @ ValDef(_, tpt, rhs) = vdef0
val valName = normalizeName(vdef, tpt).asTermName
var mods1 = vdef.mods
val vdef1 = cpy.ValDef(vdef)(name = valName).withMods(mods1)
if isSetterNeeded(vdef) then
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 syntheticUnitLiteral
val setter = cpy.DefDef(vdef)(
name = valName.setterName,
paramss = (setterParam :: Nil) :: Nil,
tpt = TypeTree(defn.UnitType),
rhs = setterRhs
).withMods((vdef.mods | Accessor) &~ (CaseAccessor | GivenOrImplicit | Lazy))
.dropEndMarker() // the end marker should only appear on the getter definition
Thicket(vdef1, setter)
else vdef1
end valDef
def mapParamss(paramss: List[ParamClause])
(mapTypeParam: TypeDef => TypeDef)
(mapTermParam: ValDef => ValDef)(using Context): List[ParamClause] =
paramss.mapConserve {
case TypeDefs(tparams) => tparams.mapConserve(mapTypeParam)
case ValDefs(vparams) => vparams.mapConserve(mapTermParam)
case _ => unreachable()
}
/** 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"
*/
private def defDef(meth: DefDef, isPrimaryConstructor: Boolean = false)(using Context): Tree =
addDefaultGetters(elimContextBounds(meth, isPrimaryConstructor).asInstanceOf[DefDef])
/** Drop context bounds in given TypeDef, replacing them with evidence ValDefs that
* get added to a buffer.
* @param tdef The given TypeDef
* @param evidenceBuf The buffer to which evidence gets added. This buffer
* is shared between desugarings of different type parameters
* of the same method.
* @param evidenceFlags The flags to use for evidence definitions
* @param freshName A function to generate fresh names for evidence definitions
* @param allParamss If `tdef` is a type paramter, all parameters of the owning method,
* otherwise the empty list.
*/
private def desugarContextBounds(
tdef: TypeDef,
evidenceBuf: mutable.ListBuffer[ValDef],
evidenceFlags: FlagSet,
freshName: untpd.Tree => TermName,
allParamss: List[ParamClause])(using Context): TypeDef =
val evidenceNames = mutable.ListBuffer[TermName]()
def desugarRHS(rhs: Tree): Tree = rhs match
case ContextBounds(tbounds, ctxbounds) =>
val isMember = evidenceFlags.isAllOf(DeferredGivenFlags)
for bound <- ctxbounds do
val evidenceName = bound match
case ContextBoundTypeTree(_, _, ownName) if !ownName.isEmpty =>
ownName // if there is an explicitly given name, use it.
case _ =>
if Config.nameSingleContextBounds
&& !isMember
&& ctxbounds.tail.isEmpty
&& Feature.enabled(Feature.modularity)
then tdef.name.toTermName
else freshName(bound)
evidenceNames += evidenceName
val evidenceParam = ValDef(evidenceName, bound, EmptyTree).withFlags(evidenceFlags)
evidenceParam.pushAttachment(ContextBoundParam, ())
evidenceBuf += evidenceParam
tbounds
case LambdaTypeTree(tparams, body) =>
cpy.LambdaTypeTree(rhs)(tparams, desugarRHS(body))
case _ =>
rhs
val tdef1 = cpy.TypeDef(tdef)(rhs = desugarRHS(tdef.rhs))
// Under x.modularity, if there was a context bound, and `tdef`s name as a term name is
// neither a name of an existing parameter nor a name of generated evidence for
// the same method, add a WitnessAnnotation with all generated evidence names to `tdef`.
// This means a context bound proxy will be created later.
if Feature.enabled(Feature.modularity)
&& evidenceNames.nonEmpty
&& !evidenceBuf.exists(_.name == tdef.name.toTermName)
&& !allParamss.nestedExists(_.name == tdef.name.toTermName)
then
tdef1.withAddedAnnotation:
WitnessNamesAnnot(evidenceNames.toList).withSpan(tdef.span)
else
tdef1
end desugarContextBounds
def elimContextBounds(meth: Tree, isPrimaryConstructor: Boolean = false)(using Context): Tree =
val evidenceParamBuf = mutable.ListBuffer[ValDef]()
var seenContextBounds: Int = 0
def freshName(unused: Tree) =
seenContextBounds += 1 // Start at 1 like FreshNameCreator.
ContextBoundParamName(EmptyTermName, seenContextBounds)
// Just like with `makeSyntheticParameter` on nameless parameters of
// using clauses, we only need names that are unique among the
// parameters of the method since shadowing does not affect
// implicit resolution in Scala 3.
def paramssNoContextBounds(paramss: List[ParamClause]): List[ParamClause] =
val iflag = paramss.lastOption.flatMap(_.headOption) match
case Some(param) if param.mods.isOneOf(GivenOrImplicit) =>
param.mods.flags & GivenOrImplicit
case _ =>
if Feature.sourceVersion.isAtLeast(`3.6`) then Given
else Implicit
val flags = if isPrimaryConstructor then iflag | LocalParamAccessor else iflag | Param
mapParamss(paramss) {
tparam => desugarContextBounds(tparam, evidenceParamBuf, flags, freshName, paramss)
}(identity)
meth match
case meth @ DefDef(_, paramss, tpt, rhs) =>
val newParamss = paramssNoContextBounds(paramss)
rhs match
case MacroTree(call) =>
cpy.DefDef(meth)(rhs = call).withMods(meth.mods | Macro | Erased)
case _ =>
addEvidenceParams(
cpy.DefDef(meth)(
name = normalizeName(meth, tpt).asTermName,
paramss = newParamss
),
evidenceParamBuf.toList
)
case meth @ PolyFunction(tparams, fun) =>
val PolyFunction(tparams: List[untpd.TypeDef] @unchecked, fun) = meth: @unchecked
val Function(vparams: List[untpd.ValDef] @unchecked, rhs) = fun: @unchecked
val newParamss = paramssNoContextBounds(tparams :: vparams :: Nil)
val params = evidenceParamBuf.toList
if params.isEmpty then
meth
else
val boundNames = getBoundNames(params, newParamss)
val recur = fitEvidenceParams(params, nme.apply, boundNames)
val (paramsFst, paramsSnd) = recur(newParamss)
functionsOf((paramsFst ++ paramsSnd).filter(_.nonEmpty), rhs)
end elimContextBounds
def addDefaultGetters(meth: DefDef)(using Context): Tree =
/** The longest prefix of parameter lists in paramss whose total number of
* ValDefs does not exceed `n`
*/
def takeUpTo(paramss: List[ParamClause], n: Int): List[ParamClause] = paramss match
case ValDefs(vparams) :: paramss1 =>
val len = vparams.length
if len <= n then vparams :: takeUpTo(paramss1, n - len) else Nil
case TypeDefs(tparams) :: paramss1 =>
tparams :: takeUpTo(paramss1, n)
case _ =>
Nil
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))
def paramssNoRHS = mapParamss(meth.paramss)(identity) {
vparam =>
if vparam.rhs.isEmpty then vparam
else cpy.ValDef(vparam)(rhs = EmptyTree).withMods(vparam.mods | HasDefault)
}
def getterParamss(n: Int): List[ParamClause] =
mapParamss(takeUpTo(paramssNoRHS, n)) {
tparam => dropContextBounds(toMethParam(tparam, KeepAnnotations.All))
} {
vparam => toMethParam(vparam, KeepAnnotations.All, keepDefault = false)
}
def defaultGetters(paramss: List[ParamClause], n: Int): List[DefDef] = paramss match
case ValDefs(vparam :: vparams) :: paramss1 =>
def defaultGetter: DefDef =
DefDef(
name = DefaultGetterName(meth.name, n),
paramss = getterParamss(n),
tpt = TypeTree(),
rhs = vparam.rhs
)
.withMods(Modifiers(
meth.mods.flags & (AccessFlags | Synthetic) | (vparam.mods.flags & Inline),
meth.mods.privateWithin))
val rest = defaultGetters(vparams :: paramss1, n + 1)
if vparam.rhs.isEmpty then rest else defaultGetter :: rest
case _ :: paramss1 => // skip empty parameter lists and type parameters
defaultGetters(paramss1, n)
case Nil =>
Nil
val defGetters = defaultGetters(meth.paramss, 0)
if defGetters.isEmpty then meth
else Thicket(cpy.DefDef(meth)(paramss = paramssNoRHS) :: defGetters)
end addDefaultGetters
/** 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)(using Context): untpd.Tree = {
def adaptToExpectedTpt(tree: untpd.Tree): untpd.Tree = tree match {
// Add the expected type as an ascription
case _: untpd.SplicePattern =>
untpd.Typed(tree, expectedTpt).withSpan(tree.span)
case Typed(expr: untpd.SplicePattern, 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)
}
/** Split out the quoted pattern type variable definition from the pattern.
*
* Type variable definitions are all the `type t` defined at the start of a quoted pattern.
* Where name `t` is a pattern type variable name (i.e. lower case letters).
*
* ```
* type t1; ...; type tn;
* ```
* is split into
* ```
* (List(; ...; ), )
* ```
*/
def quotedPatternTypeVariables(tree: untpd.Tree)(using Context): (List[untpd.TypeDef], untpd.Tree) =
tree match
case untpd.Block(stats, expr) =>
val (untpdTypeVariables, otherStats) = stats.span {
case tdef @ untpd.TypeDef(name, _) => !tdef.isBackquoted && name.isVarPattern
case _ => false
}
val untpdCaseTypeVariables = untpdTypeVariables.asInstanceOf[List[untpd.TypeDef]].map {
tdef => tdef.withMods(tdef.mods | Case)
}
val pattern = if otherStats.isEmpty then expr else untpd.cpy.Block(tree)(otherStats, expr)
(untpdCaseTypeVariables, pattern)
case _ =>
(Nil, tree)
private def referencesName(vdef: ValDef, names: Set[TermName])(using Context): Boolean =
vdef.tpt.existsSubTree:
case Ident(name: TermName) => names.contains(name)
case _ => false
/** Fit evidence `params` into the `mparamss` parameter lists, making sure
* that all parameters referencing `params` are after them.
* - for methods the final parameter lists are := result._1 ++ result._2
* - for poly functions, each element of the pair contains at most one term
* parameter list
*
* @param params the evidence parameters list that should fit into `mparamss`
* @param methName the name of the method that `mparamss` belongs to
* @param boundNames the names of the evidence parameters
* @param mparamss the original parameter lists of the method
* @return a pair of parameter lists containing all parameter lists in a
* reference-correct order; make sure that `params` is always at the
* intersection of the pair elements; this is relevant, for poly functions
* where `mparamss` is guaranteed to have exectly one term parameter list,
* then each pair element will have at most one term parameter list
*/
private def fitEvidenceParams(
params: List[ValDef],
methName: Name,
boundNames: Set[TermName]
)(mparamss: List[ParamClause])(using Context): (List[ParamClause], List[ParamClause]) = mparamss match
case ValDefs(mparams) :: _ if mparams.exists(referencesName(_, boundNames)) =>
(params :: Nil) -> mparamss
case ValDefs(mparams @ (mparam :: _)) :: Nil if mparam.mods.isOneOf(GivenOrImplicit) =>
val normParams =
if params.head.mods.flags.is(Given) != mparam.mods.flags.is(Given) then
params.map: param =>
val normFlags = param.mods.flags &~ GivenOrImplicit | (mparam.mods.flags & (GivenOrImplicit))
param.withMods(param.mods.withFlags(normFlags))
.showing(i"adapted param $result ${result.mods.flags} for ${methName}", Printers.desugar)
else params
((normParams ++ mparams) :: Nil) -> Nil
case mparams :: mparamss1 =>
val (fst, snd) = fitEvidenceParams(params, methName, boundNames)(mparamss1)
(mparams :: fst) -> snd
case Nil =>
Nil -> (params :: Nil)
/** Create a chain of possibly contextual functions from the parameter lists */
private def functionsOf(paramss: List[ParamClause], rhs: Tree)(using Context): Tree = paramss match
case Nil => rhs
case ValDefs(head @ (fst :: _)) :: rest if fst.mods.isOneOf(GivenOrImplicit) =>
val paramTpts = head.map(_.tpt)
val paramNames = head.map(_.name)
val paramsErased = head.map(_.mods.flags.is(Erased))
makeContextualFunction(paramTpts, paramNames, functionsOf(rest, rhs), paramsErased).withSpan(rhs.span)
case ValDefs(head) :: rest =>
Function(head, functionsOf(rest, rhs))
case TypeDefs(head) :: rest =>
PolyFunction(head, functionsOf(rest, rhs))
case _ =>
assert(false, i"unexpected paramss $paramss")
EmptyTree
private def getBoundNames(params: List[ValDef], paramss: List[ParamClause])(using Context): Set[TermName] =
var boundNames = params.map(_.name).toSet // all evidence parameter + context bound proxy names
for mparams <- paramss; mparam <- mparams do
mparam match
case tparam: TypeDef if tparam.mods.annotations.exists(WitnessNamesAnnot.unapply(_).isDefined) =>
boundNames += tparam.name.toTermName
case _ =>
boundNames
/** Add all evidence parameters in `params` as implicit parameters to `meth`.
* The position of the added parameters is determined as follows:
*
* - If there is an existing parameter list that refers to one of the added
* parameters or their future context bound proxies in one of its parameter
* types, add the new parameters in front of the first such parameter list.
* - Otherwise, if the last parameter list consists of implicit or using parameters,
* join the new parameters in front of this parameter list, creating one
* parameter list (this is equivalent to Scala 2's scheme).
* - Otherwise, add the new parameter list at the end as a separate parameter clause.
*/
private def addEvidenceParams(meth: DefDef, params: List[ValDef])(using Context): DefDef =
if params.isEmpty then return meth
val boundNames = getBoundNames(params, meth.paramss)
val fitParams = fitEvidenceParams(params, meth.name, boundNames)
if meth.removeAttachment(PolyFunctionApply).isDefined then
// for PolyFunctions we are limited to a single term param list, so we
// reuse the fitEvidenceParams logic to compute the new parameter lists
// and then we add the other parameter lists as function types to the
// return type
val (paramsFst, paramsSnd) = fitParams(meth.paramss)
if ctx.mode.is(Mode.Type) then
cpy.DefDef(meth)(paramss = paramsFst, tpt = functionsOf(paramsSnd, meth.tpt))
else
cpy.DefDef(meth)(paramss = paramsFst, rhs = functionsOf(paramsSnd, meth.rhs))
else
val (paramsFst, paramsSnd) = fitParams(meth.paramss)
cpy.DefDef(meth)(paramss = paramsFst ++ paramsSnd)
end addEvidenceParams
/** The parameters generated from the contextual bounds of `meth`, as generated by `desugar.defDef` */
private def evidenceParams(meth: DefDef)(using Context): List[ValDef] =
for
case ValDefs(vparams @ (vparam :: _)) <- meth.paramss
if vparam.mods.isOneOf(GivenOrImplicit)
param <- vparams.takeWhile(_.hasAttachment(ContextBoundParam))
yield
param
@sharable private val synthetic = Modifiers(Synthetic)
/** Which annotations to keep in derived parameters */
private enum KeepAnnotations:
case None, All, WitnessOnly
/** Filter annotations in `mods` according to `keep` */
private def filterAnnots(mods: Modifiers, keep: KeepAnnotations)(using Context) = keep match
case KeepAnnotations.None => mods.withAnnotations(Nil)
case KeepAnnotations.All => mods
case KeepAnnotations.WitnessOnly =>
mods.withAnnotations:
mods.annotations.filter:
case WitnessNamesAnnot(_) => true
case _ => false
/** Map type parameter accessor to corresponding method (i.e. constructor) parameter */
private def toMethParam(tparam: TypeDef, keep: KeepAnnotations)(using Context): TypeDef =
val mods = filterAnnots(tparam.rawMods, keep)
tparam.withMods(mods & EmptyFlags | Param)
/** Map term parameter accessor to corresponding method (i.e. constructor) parameter */
private def toMethParam(vparam: ValDef, keep: KeepAnnotations, keepDefault: Boolean)(using Context): ValDef = {
val mods = filterAnnots(vparam.rawMods, keep)
val hasDefault = if keepDefault then HasDefault else EmptyFlags
// Need to ensure that tree is duplicated since term parameters can be watched
// and cloning a term parameter will copy its watchers to the clone, which means
// we'd get cross-talk between the original parameter and the clone.
ValDef(vparam.name, vparam.tpt, vparam.rhs)
.withSpan(vparam.span)
.withAttachmentsFrom(vparam)
.withMods(mods & (GivenOrImplicit | Erased | hasDefault | Tracked) | Param)
}
/** Desugar type def (not param): Under x.moduliity this can expand
* context bounds, which are expanded to evidence ValDefs. These will
* ultimately map to deferred givens.
*/
def typeDef(tdef: TypeDef)(using Context): Tree =
val evidenceBuf = new mutable.ListBuffer[ValDef]
val result = desugarContextBounds(
tdef, evidenceBuf,
(tdef.mods.flags.toTermFlags & AccessFlags) | Lazy | DeferredGivenFlags,
inventGivenName, Nil)
if evidenceBuf.isEmpty then result else Thicket(result :: evidenceBuf.toList)
/** The expansion of a class definition. See inline comments for what is involved */
def classDef(cdef: TypeDef)(using Context): Tree = {
val impl @ Template(constr0, _, self, _) = cdef.rhs: @unchecked
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)
.withAnnotations(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
def isNonEnumCase = !isEnumCase && (isCaseClass || isCaseObject)
val isValueClass = parents.nonEmpty && isAnyVal(parents.head)
// This is not watertight, but `extends AnyVal` will be replaced by `inline` later.
val caseClassInScala2Library = isCaseClass && ctx.settings.YcompileScala2Library.value
val originalTparams = constr1.leadingTypeParams
val originalVparamss = asTermOnly(constr1.trailingParamss)
lazy val derivedEnumParams = enumClass.typeParams.map(derivedTypeParamWithVariance)
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
if mods.is(Trait) then
for vparams <- originalVparamss; vparam <- vparams do
if isByNameType(vparam.tpt) then
report.error(em"implementation restriction: traits cannot have by name parameters", vparam.srcPos)
// Annotations on class _type_ parameters are set on the derived parameters
// but not on the constructor parameters. The reverse is true for
// annotations on class _value_ parameters.
val constrTparams = impliedTparams.map(toMethParam(_, KeepAnnotations.WitnessOnly))
val constrVparamss =
if (originalVparamss.isEmpty) { // ensure parameter list is non-empty
if (isCaseClass)
report.error(CaseClassMissingParamList(cdef), namePos)
ListOfNil
}
else if (isCaseClass && originalVparamss.head.exists(_.mods.isOneOf(GivenOrImplicit))) {
report.error(CaseClassMissingNonImplicitParamList(cdef), namePos)
ListOfNil
}
else originalVparamss.nestedMap(toMethParam(_, KeepAnnotations.All, keepDefault = true))
val derivedTparams =
constrTparams.zipWithConserve(impliedTparams)((tparam, impliedParam) =>
derivedTypeParam(tparam).withAnnotations(impliedParam.mods.annotations))
val derivedVparamss =
constrVparamss.nestedMap: vparam =>
val derived =
if ctx.compilationUnit.isJava then derivedTermParam(vparam, Parsers.unimplementedExpr)
else derivedTermParam(vparam)
derived.withAnnotations(Nil)
val constr = cpy.DefDef(constr1)(paramss = joinParams(constrTparams, 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)(paramss = joinParams(constrTparams, ddef.paramss)),
evidenceParams(constr1).map(toMethParam(_, KeepAnnotations.None, keepDefault = false)))))
case stat =>
stat
}
// The Identifiers defined by a case
def caseIds(tree: Tree): List[Ident] = tree match {
case tree: MemberDef => Ident(tree.name.toTermName) :: Nil
case PatDef(_, ids: List[Ident] @ unchecked, _, _) => ids
}
val stats0 = impl.body.map(expandConstructor)
val stats =
if (ctx.owner eq defn.ScalaPackageClass) && defn.hasProblematicGetClass(className) then
stats0.filterConserve {
case ddef: DefDef =>
ddef.name ne nme.getClass_
case _ =>
true
}
else
stats0
if (isEnum) {
val (enumCases, enumStats) = stats.partition(DesugarEnums.isEnumCase)
if (enumCases.isEmpty)
report.error(EnumerationsShouldNotBeEmpty(cdef), namePos)
else
enumCases.last.pushAttachment(DesugarEnums.DefinesEnumLookupMethods, ())
val enumCompanionRef = TermRefTree()
val enumImport =
Import(enumCompanionRef, enumCases.flatMap(caseIds).map(
enumCase =>
ImportSelector(enumCase.withSpan(enumCase.span.startPos))
)
)
(enumImport :: enumStats, enumCases, enumCompanionRef)
}
else (stats, Nil, EmptyTree)
}
def anyRef = ref(defn.AnyRefAlias.typeRef)
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
}
/** Is this a repeated argument x* (using a spread operator)? */
def isRepeated(tree: Tree): Boolean = stripByNameType(tree) match
case PostfixOp(_, Ident(tpnme.raw.STAR)) => true
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(_ => WildcardTypeBoundsTree()))
case TypeDef(_, rhs: DerivedTypeTree) =>
fullyApplied(rhs.watched)
case _ =>
targ
}
if (widenHK) fullyApplied(tparam) else targ
}
appliedTypeTree(tycon, targs)
}
// 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 {
report.error(TypedCaseDoesNotExplicitlyExtendTypedEnum(enumClass, cdef)
, cdef.srcPos.startPos)
appliedTypeTree(enumClassRef, constrTparams map (_ => anyRef))
}
// new C[Ts](paramss)
lazy val creatorExpr =
val vparamss = constrVparamss match
case (vparam :: _) :: _ if vparam.mods.is(Implicit) => // add a leading () to match class parameters
Nil :: constrVparamss
case _ =>
if constrVparamss.nonEmpty && constrVparamss.forall {
case vparam :: _ => vparam.mods.is(Given)
case _ => false
}
then constrVparamss :+ Nil // add a trailing () to match class parameters
else constrVparamss
val nu = vparamss.foldLeft(makeNew(classTypeRef)) { (nu, vparams) =>
val app = Apply(nu, vparams.map(refOfDef))
vparams match {
case vparam :: _ if vparam.mods.is(Given) || vparam.name.is(ContextBoundParamName) =>
app.setApplyKind(ApplyKind.Using)
case _ => app
}
}
ensureApplied(nu)
val copiedAccessFlags = if Feature.migrateTo3 then 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 (unless already given as valdef or parameterless defdef)
// def copy(p1: T1 = p1..., pN: TN = pN)(moreParams) =
// new C[...](p1, ..., pN)(moreParams)
val (caseClassMeths, enumScaffolding) = {
def syntheticProperty(name: TermName, tpt: Tree, rhs: Tree) =
DefDef(name, Nil, tpt, rhs).withMods(synthetic)
def productElemMeths =
if caseClassInScala2Library then Nil
else
val caseParams = derivedVparamss.head.toArray
val selectorNamesInBody = normalizedBody.collect {
case vdef: ValDef if vdef.name.isSelectorName =>
vdef.name
case ddef: DefDef if ddef.name.isSelectorName && ddef.paramss.isEmpty =>
ddef.name
}
for i <- List.range(0, arity)
selName = nme.selectorName(i)
if (selName ne caseParams(i).name) && !selectorNamesInBody.contains(selName)
yield syntheticProperty(selName, caseParams(i).tpt,
Select(This(EmptyTypeIdent), caseParams(i).name))
def enumCaseMeths =
if isEnumCase then
val (ordinal, scaffolding) = nextOrdinal(className, CaseKind.Class, definesEnumLookupMethods(cdef))
(ordinalMethLit(ordinal) :: Nil, scaffolding)
else (Nil, Nil)
def copyMeths = {
val hasRepeatedParam = constrVparamss.nestedExists {
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 {
val copyFirstParams = derivedVparamss.head.map(vparam =>
cpy.ValDef(vparam)(rhs = refOfDef(vparam)))
val copyRestParamss = derivedVparamss.tail.nestedMap(vparam =>
cpy.ValDef(vparam)(rhs = EmptyTree))
var flags = Synthetic | constr1.mods.flags & copiedAccessFlags
if ctx.settings.YcompileScala2Library.value then flags &~= Private
DefDef(
nme.copy,
joinParams(derivedTparams, copyFirstParams :: copyRestParamss),
TypeTree(),
creatorExpr
).withMods(Modifiers(flags, constr1.mods.privateWithin)) :: Nil
}
}
if isCaseClass then
val (enumMeths, enumScaffolding) = enumCaseMeths
(copyMeths ::: enumMeths ::: productElemMeths, enumScaffolding)
else (Nil, Nil)
}
var parents1 = parents
if (isEnumCase && parents.isEmpty)
parents1 = enumClassTypeRef :: Nil
if (isNonEnumCase)
parents1 = parents1 :+ scalaDot(str.Product.toTypeName) :+ scalaDot(nme.Serializable.toTypeName)
if (isEnum)
parents1 = parents1 :+ ref(defn.EnumClass)
// 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 (case modClsDef @ TypeDef(_, _) <- mdefs)
modClsDef.putAttachment(DerivingCompanion, impl.srcPos.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) {
val applyMeths =
if (mods.is(Abstract)) Nil
else {
val appMods =
var flags = Synthetic | constr1.mods.flags & copiedAccessFlags
if ctx.settings.YcompileScala2Library.value then flags &~= Private
Modifiers(flags).withPrivateWithin(constr1.mods.privateWithin)
val appParamss =
derivedVparamss.nestedZipWithConserve(constrVparamss)((ap, cp) =>
ap.withMods(ap.mods | (cp.mods.flags & HasDefault)))
DefDef(nme.apply, joinParams(derivedTparams, appParamss), TypeTree(), creatorExpr)
.withMods(appMods) :: Nil
}
val unapplyMeth = {
def scala2LibCompatUnapplyRhs(unapplyParamName: Name) =
assert(arity <= Definitions.MaxTupleArity, "Unexpected case class with tuple larger than 22: "+ cdef.show)
derivedVparamss.head match
case vparam :: Nil =>
Apply(scalaDot(nme.Option), Select(Ident(unapplyParamName), vparam.name))
case vparams =>
val tupleApply = Select(Ident(nme.scala), s"Tuple$arity".toTermName)
val members = vparams.map(vparam => Select(Ident(unapplyParamName), vparam.name))
Apply(scalaDot(nme.Option), Apply(tupleApply, members))
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 if caseClassInScala2Library then scala2LibCompatUnapplyRhs(unapplyParam.name)
else Ident(unapplyParam.name)
val unapplyResTp = if (arity == 0) Literal(Constant(true)) else TypeTree()
DefDef(
methName,
joinParams(derivedTparams, (unapplyParam :: Nil) :: Nil),
unapplyResTp,
unapplyRHS
).withMods(synthetic)
}
val toStringMeth =
DefDef(nme.toString_, Nil, TypeTree(), Literal(Constant(className.toString))).withMods(Modifiers(Override | Synthetic))
companionDefs(anyRef, applyMeths ::: unapplyMeth :: toStringMeth :: companionMembers)
}
else if (companionMembers.nonEmpty || companionDerived.nonEmpty || isEnum)
companionDefs(anyRef, companionMembers)
else if isValueClass && !isObject then
companionDefs(anyRef, Nil)
else Nil
enumCompanionRef match {
case ref: TermRefTree => // have the enum import watch the companion object
val (modVal: ValDef) :: _ = companions: @unchecked
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(GivenOrImplicit))
Nil
else if (ctx.owner.is(Package)) {
report.error(TopLevelImplicitClass(cdef), cdef.srcPos)
Nil
}
else if (mods.is(Trait)) {
report.error(TypesAndTraitsCantBeImplicit(), cdef.srcPos)
Nil
}
else if (isCaseClass) {
report.error(ImplicitCaseClass(cdef), cdef.srcPos)
Nil
}
else if (arity != 1 && !mods.is(Given)) {
report.error(ImplicitClassPrimaryConstructorArity(), cdef.srcPos)
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
val finalFlag = if ctx.settings.YcompileScala2Library.value then EmptyFlags else Final
// implicit wrapper is typechecked in same scope as constructor, so
// we can reuse the constructor parameters; no derived params are needed.
DefDef(
className.toTermName, joinParams(constrTparams, defParamss), classTypeRef, creatorExpr
) .withMods(companionMods | mods.flags.toTermFlags & (GivenOrImplicit | Inline) | finalFlag)
.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 tparamAccessors = {
val impliedTparamsIt = impliedTparams.iterator
derivedTparams.map(_.withMods(impliedTparamsIt.next().mods))
}
val caseAccessor = if (isCaseClass) CaseAccessor else EmptyFlags
val vparamAccessors = {
val originalVparamsIt = originalVparamss.iterator.flatten
derivedVparamss match {
case first :: rest =>
first.map(_.withMods(originalVparamsIt.next().mods | caseAccessor)) ++
rest.flatten.map(_.withMods(originalVparamsIt.next().mods))
case _ =>
Nil
}
}
if mods.isAllOf(Given | Inline | Transparent) then
report.error("inline given instances cannot be trasparent", cdef)
var classMods = if mods.is(Given) then mods &~ (Inline | Transparent) | Synthetic else mods
if vparamAccessors.exists(_.mods.is(Tracked)) then
classMods |= Dependent
cpy.TypeDef(cdef: TypeDef)(
name = className,
rhs = cpy.Template(impl)(constr, parents1, clsDerived, self1,
tparamAccessors ::: vparamAccessors ::: normalizedBody ::: caseClassMeths)
).withMods(classMods)
}
// install the watch on classTycon
classTycon match {
case tycon: DerivedTypeTree => tycon.watching(cdef1)
case _ =>
}
flatTree(cdef1 :: companions ::: implicitWrappers ::: enumScaffolding)
}.showing(i"desugared: $cdef --> $result", Printers.desugar)
/** Expand
*
* package object name { body }
*
* to:
*
* package name {
* object `package` { body }
* }
*/
def packageModuleDef(mdef: ModuleDef)(using Context): Tree =
val impl = mdef.impl
val mods = mdef.mods
val moduleName = normalizeName(mdef, impl).asTermName
if mods.is(Package) then
checkPackageName(mdef)
PackageDef(Ident(moduleName),
cpy.ModuleDef(mdef)(nme.PACKAGE, impl).withMods(mods &~ Package) :: Nil)
else
mdef
/** 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)(using Context): Tree = {
val impl = mdef.impl
val mods = mdef.mods
val moduleName = normalizeName(mdef, impl).asTermName
def isEnumCase = mods.isEnumCase
Checking.checkWellFormedModule(mdef)
if (mods.is(Package))
packageModuleDef(mdef)
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, definesEnumLookupMethods(mdef), 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) report.error(ObjectMayNotHaveSelfType(mdef), impl.self.srcPos)
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)
.withEndMarker(copyFrom = mdef) // copy over the end marker position to the module class def
Thicket(modul, classDef(cls).withSpan(mdef.span))
}
}
def extMethod(mdef: DefDef, extParamss: List[ParamClause])(using Context): DefDef =
cpy.DefDef(mdef)(
name = normalizeName(mdef, mdef.tpt).asTermName,
paramss =
if mdef.name.isRightAssocOperatorName then
val (rightTyParams, paramss) = mdef.paramss.span(isTypeParamClause) // first extract type parameters
paramss match
case rightParam :: paramss1 => // `rightParam` must have a single parameter and without `given` flag
def badRightAssoc(problem: String, pos: SrcPos) =
report.error(em"right-associative extension method $problem", pos)
extParamss ++ mdef.paramss
rightParam match
case ValDefs(vparam :: Nil) =>
if !vparam.mods.is(Given) then
// we merge the extension parameters with the method parameters,
// swapping the operator arguments:
// e.g.
// extension [A](using B)(c: C)(using D)
// def %:[E](f: F)(g: G)(using H): Res = ???
// will be encoded as
// def %:[A](using B)[E](f: F)(c: C)(using D)(g: G)(using H): Res = ???
//
// If you change the names of the clauses below, also change them in right-associative-extension-methods.md
val (leftTyParamsAndLeadingUsing, leftParamAndTrailingUsing) = extParamss.span(isUsingOrTypeParamClause)
val names = (for ps <- mdef.paramss; p <- ps yield p.name).toSet[Name]
val tt = new untpd.UntypedTreeTraverser:
def traverse(tree: Tree)(using Context): Unit = tree match
case tree: Ident if names.contains(tree.name) =>
badRightAssoc(s"cannot have a forward reference to ${tree.name}", tree.srcPos)
case _ => traverseChildren(tree)
for ts <- leftParamAndTrailingUsing; t <- ts do
tt.traverse(t)
leftTyParamsAndLeadingUsing ::: rightTyParams ::: rightParam :: leftParamAndTrailingUsing ::: paramss1
else
badRightAssoc("cannot start with using clause", mdef.srcPos)
case _ =>
badRightAssoc("must start with a single parameter", mdef.srcPos)
case _ =>
// no value parameters, so not an infix operator.
extParamss ++ mdef.paramss
else
extParamss ++ mdef.paramss
).withMods(mdef.mods | ExtensionMethod)
/** Transform extension construct to list of extension methods */
def extMethods(ext: ExtMethods)(using Context): Tree = flatTree {
ext.methods map {
case exp: Export => exp
case mdef: DefDef => defDef(extMethod(mdef, ext.paramss))
}
}
/** Transforms
*
* type t >: Low <: Hi
* to
*
* @patternType type $T >: Low <: Hi
*
* if the type has a pattern variable name
*/
def quotedPatternTypeDef(tree: TypeDef)(using Context): TypeDef = {
assert(ctx.mode.isQuotedPattern)
if tree.name.isVarPattern && !tree.isBackquoted then
val patternTypeAnnot = New(ref(defn.QuotedRuntimePatterns_patternTypeAnnot.typeRef)).withSpan(tree.span)
val mods = tree.mods.withAddedAnnotation(patternTypeAnnot)
tree.withMods(mods)
else if tree.name.startsWith("$") && !tree.isBackquoted then
report.error(
"""Quoted pattern variable names starting with $ are not supported anymore.
|Use lower cases type pattern name instead.
|""".stripMargin,
tree.srcPos)
tree
else tree
}
def checkPackageName(mdef: ModuleDef | PackageDef)(using Context): Unit =
def check(name: Name, errSpan: Span): Unit = name match
case name: SimpleName if !errSpan.isSynthetic && name.exists(Chars.willBeEncoded) =>
report.warning(em"The package name `$name` will be encoded on the classpath, and can lead to undefined behaviour.", mdef.source.atSpan(errSpan))
case _ =>
def loop(part: RefTree): Unit = part match
case part @ Ident(name) => check(name, part.span)
case part @ Select(qual: RefTree, name) =>
check(name, part.nameSpan)
loop(qual)
case _ =>
mdef match
case pdef: PackageDef => loop(pdef.pid)
case mdef: ModuleDef if mdef.mods.is(Package) => check(mdef.name, mdef.nameSpan)
case _ =>
end checkPackageName
/** 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)(using Context): Name = {
var name = mdef.name
if (name.isEmpty) name = name.likeSpaced(inventGivenName(impl))
def errPos = mdef.source.atSpan(mdef.nameSpan)
if (ctx.owner == defn.ScalaPackageClass && defn.reservedScalaClassNames.contains(name.toTypeName)) {
val kind = if (name.isTypeName) "class" else "object"
report.error(IllegalRedefinitionOfStandardKind(kind, name), errPos)
name = name.errorName
}
name
}
/** Strip parens and empty blocks around the body of `tree`. */
def normalizePolyFunction(tree: PolyFunction)(using Context): PolyFunction =
def stripped(body: Tree): Tree = body match
case Parens(body1) =>
stripped(body1)
case Block(Nil, body1) =>
stripped(body1)
case _ => body
cpy.PolyFunction(tree)(tree.targs, stripped(tree.body)).asInstanceOf[PolyFunction]
/** Desugar [T_1, ..., T_M] => (P_1, ..., P_N) => R
* Into scala.PolyFunction { def apply[T_1, ..., T_M](x$1: P_1, ..., x$N: P_N): R }
*/
def makePolyFunctionType(tree: PolyFunction)(using Context): RefinedTypeTree = (tree: @unchecked) match
case PolyFunction(tparams: List[untpd.TypeDef] @unchecked, fun @ untpd.Function(vparamTypes, res)) =>
val paramFlags = fun match
case fun: FunctionWithMods =>
// TODO: make use of this in the desugaring when pureFuns is enabled.
// val isImpure = funFlags.is(Impure)
// Function flags to be propagated to each parameter in the desugared method type.
val givenFlag = fun.mods.flags.toTermFlags & Given
fun.erasedParams.map(isErased => if isErased then givenFlag | Erased else givenFlag)
case _ =>
vparamTypes.map(_ => EmptyFlags)
val vparams = vparamTypes.lazyZip(paramFlags).zipWithIndex.map {
case ((p: ValDef, paramFlags), n) => p.withAddedFlags(paramFlags)
case ((p, paramFlags), n) => makeSyntheticParameter(n + 1, p).withAddedFlags(paramFlags)
}.toList
RefinedTypeTree(ref(defn.PolyFunctionType), List(
DefDef(nme.apply, tparams :: vparams :: Nil, res, EmptyTree)
.withFlags(Synthetic)
.withAttachment(PolyFunctionApply, ())
)).withSpan(tree.span)
end makePolyFunctionType
/** Invent a name for an anonympus given of type or template `impl`. */
def inventGivenName(impl: Tree)(using Context): SimpleName =
val str = impl match
case impl: Template =>
if impl.parents.isEmpty then
report.error(AnonymousInstanceCannotBeEmpty(impl), impl.srcPos)
nme.ERROR.toString
else
impl.parents.map(inventTypeName(_)).mkString("given_", "_", "")
case impl: Tree =>
"given_" ++ inventTypeName(impl)
str.toTermName.asSimpleName
/** Extract a synthesized given name from a type tree. This is used for
* both anonymous givens and deferred givens.
* @param followArgs if true include argument types in the name
*/
private class NameExtractor(followArgs: Boolean) extends UntypedTreeAccumulator[String] {
private def extractArgs(args: List[Tree])(using Context): String =
args.map(argNameExtractor.apply("", _)).mkString("_")
override def apply(x: String, tree: Tree)(using Context): String =
if (x.isEmpty)
tree match {
case Select(pre, nme.CONSTRUCTOR) => foldOver(x, pre)
case tree: RefTree =>
if tree.name.isTypeName then tree.name.toString
else s"${tree.name}_type"
case tree: TypeDef => tree.name.toString
case tree: AppliedTypeTree if followArgs && tree.args.nonEmpty =>
s"${apply(x, tree.tpt)}_${extractArgs(tree.args)}"
case ContextBoundTypeTree(tycon, paramName, _) =>
s"${apply(x, tycon)}_$paramName"
case InfixOp(left, op, right) =>
if followArgs then s"${op.name}_${extractArgs(List(left, right))}"
else op.name.toString
case tree: LambdaTypeTree =>
apply(x, tree.body)
case tree: Tuple =>
extractArgs(tree.trees)
case tree: Function if tree.args.nonEmpty =>
if followArgs then 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)(using Context): String = typeNameExtractor("", tree)
/**This will check if this def tree is marked to define enum lookup methods,
* this is not recommended to call more than once per tree
*/
private def definesEnumLookupMethods(ddef: DefTree): Boolean =
ddef.removeAttachment(DefinesEnumLookupMethods).isDefined
/** 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)(using Context): Tree = flatTree {
val PatDef(mods, pats, tpt, rhs) = pdef
if mods.isEnumCase then
def expand(id: Ident, definesLookups: Boolean) =
expandSimpleEnumCase(id.name.asTermName, mods, definesLookups,
Span(id.span.start, id.span.end, id.span.start))
val ids = pats.asInstanceOf[List[Ident]]
if definesEnumLookupMethods(pdef) then
ids.init.map(expand(_, false)) ::: expand(ids.last, true) :: Nil
else
ids.map(expand(_, false))
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 with attachment `CheckIrrefutable -> checkMode`
*/
def makeSelector(sel: Tree, checkMode: MatchCheck)(using Context): Tree =
checkMode match
case MatchCheck.None =>
Annotated(sel, New(ref(defn.UncheckedAnnot.typeRef)))
case MatchCheck.Exhaustive =>
sel
case MatchCheck.IrrefutablePatDef | MatchCheck.IrrefutableGenFrom =>
// TODO: use `pushAttachment` and investigate duplicate attachment
sel.withAttachment(CheckIrrefutable, checkMode)
sel
end match
/** 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)(using Context): Tree = pat match {
case IdPattern(id, tpt) =>
val id1 =
if id.name == nme.WILDCARD
then cpy.Ident(id)(WildcardParamName.fresh())
else id
derivedValDef(original, id1, tpt, rhs, mods)
case _ =>
def filterWildcardGivenBinding(givenPat: Bind): Boolean =
givenPat.name != nme.WILDCARD
def errorOnGivenBinding(bind: Bind)(using Context): Boolean =
report.error(
em"""${hl("given")} patterns are not allowed in a ${hl("val")} definition,
|please bind to an identifier and use an alias given.""", bind)
false
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) && !hasNamedArg(es)
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 inAliasGenerator = original match
case _: GenAlias => true
case _ => false
val vars =
if (tupleOptimizable) // include `_`
pat match
case Tuple(pats) => pats.map { case id: Ident => id -> TypeTree() }
else
getVariables(
tree = pat,
shouldAddGiven =
if inAliasGenerator then
filterWildcardGivenBinding
else
errorOnGivenBinding
) // no `_`
val ids = for ((named, _) <- vars) yield Ident(named.name)
val matchExpr =
if (tupleOptimizable) rhs
else
val caseDef = CaseDef(pat, EmptyTree, makeTuple(ids))
Match(makeSelector(rhs, MatchCheck.IrrefutablePatDef), caseDef :: Nil)
vars match {
case Nil if !mods.is(Lazy) =>
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)
val useSelectors = vars.length <= 22
def selector(n: Int) =
if useSelectors then Select(Ident(tmpName), nme.selectorName(n))
else Apply(Select(Ident(tmpName), nme.apply), Literal(Constant(n)) :: Nil)
val restDefs =
for (((named, tpt), n) <- vars.zipWithIndex if named.name != nme.WILDCARD)
yield
if mods.is(Lazy) then
DefDef(named.name.asTermName, Nil, tpt, selector(n))
.withMods(mods &~ Lazy)
.withSpan(named.span)
else
valDef(
ValDef(named.name.asTermName, tpt, selector(n))
.withMods(mods)
.withSpan(named.span)
)
flatTree(firstDef :: restDefs)
}
}
/** Expand variable identifier x to x @ _ */
def patternVar(tree: Tree)(using Context): Bind = {
val Ident(name) = unsplice(tree): @unchecked
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 bounds: TypeBoundsTree => !bounds.alias.isEmpty
case _: Template | _: MatchTypeTree => false
case LambdaTypeTree(_, body) => rhsOK(body)
case _ => true
}
rhsOK(rhs)
}
def checkOpaqueAlias(tree: MemberDef)(using Context): MemberDef =
def check(rhs: Tree): MemberDef = rhs match
case bounds: TypeBoundsTree if bounds.alias.isEmpty =>
report.error(em"opaque type must have a right-hand side", tree.srcPos)
tree.withMods(tree.mods.withoutFlags(Opaque))
case LambdaTypeTree(_, body) => check(body)
case _ => tree
if !tree.mods.is(Opaque) then tree
else tree match
case TypeDef(_, rhs) => check(rhs)
case _ => tree
/** 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)(using Context): Tree = tree match {
case tree: MemberDef =>
var tested: MemberDef = tree
def checkApplicable(flag: Flag, test: MemberDefTest): MemberDef =
if (tested.mods.is(flag) && !test.applyOrElse(tree, (md: MemberDef) => false)) {
report.error(ModifierNotAllowedForDefinition(flag), tree.srcPos)
tested.withMods(tested.mods.withoutFlags(flag))
} else tested
tested = checkOpaqueAlias(tested)
tested = checkApplicable(Opaque, legalOpaque)
tested
case _ =>
tree
}
def defTree(tree: Tree)(using Context): Tree =
checkModifiers(tree) match {
case tree: ValDef => valDef(tree)
case tree: TypeDef =>
if (tree.isClassDef) classDef(tree)
else if (ctx.mode.isQuotedPattern) quotedPatternTypeDef(tree)
else typeDef(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)(using Context): Block = tree.expr match {
case EmptyTree =>
cpy.Block(tree)(tree.stats,
syntheticUnitLiteral.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)(using 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 sel = Select(fn, op.name).withSpan(selectPos)
if (left.sourcePos.endLine < op.sourcePos.startLine)
sel.pushAttachment(MultiLineInfix, ())
arg match
case Parens(arg) =>
Apply(sel, assignToNamedArg(arg) :: Nil)
case Tuple(args) if args.exists(_.isInstanceOf[Assign]) =>
Apply(sel, args.mapConserve(assignToNamedArg))
case Tuple(args) =>
Apply(sel, arg :: Nil).setApplyKind(ApplyKind.InfixTuple)
case _ =>
Apply(sel, arg :: Nil)
if op.name.isRightAssocOperatorName then
makeOp(right, left, Span(op.span.start, right.span.end))
else
makeOp(left, right, Span(left.span.start, op.span.end, op.span.start))
}
/** Translate throws type `A throws E1 | ... | En` to
* $throws[... $throws[A, E1] ... , En].
*/
def throws(tpt: Tree, op: Ident, excepts: Tree)(using Context): AppliedTypeTree = excepts match
case Parens(excepts1) =>
throws(tpt, op, excepts1)
case InfixOp(l, bar @ Ident(tpnme.raw.BAR), r) =>
throws(throws(tpt, op, l), bar, r)
case e =>
AppliedTypeTree(
TypeTree(defn.throwsAlias.typeRef).withSpan(op.span), tpt :: excepts :: Nil)
private def checkWellFormedTupleElems(elems: List[Tree])(using Context): List[Tree] =
val seen = mutable.Set[Name]()
for case arg @ NamedArg(name, _) <- elems do
if seen.contains(name) then
report.error(em"Duplicate tuple element name", arg.srcPos)
seen += name
if name.startsWith("_") && name.toString.tail.toIntOption.isDefined then
report.error(
em"$name cannot be used as the name of a tuple element because it is a regular tuple selector",
arg.srcPos)
elems match
case elem :: elems1 =>
val mismatchOpt =
if elem.isInstanceOf[NamedArg]
then elems1.find(!_.isInstanceOf[NamedArg])
else elems1.find(_.isInstanceOf[NamedArg])
mismatchOpt match
case Some(misMatch) =>
report.error(em"Illegal combination of named and unnamed tuple elements", misMatch.srcPos)
elems.mapConserve(stripNamedArg)
case None => elems
case _ => elems
end checkWellFormedTupleElems
/** Translate tuple expressions
*
* () ==> ()
* (t) ==> t
* (t1, ..., tN) ==> TupleN(t1, ..., tN)
* (n1 = t1, ..., nN = tN) ==> NamedTuple.build[(n1, ..., nN)]()(TupleN(t1, ..., tN))
*/
def tuple(tree: Tuple, pt: Type)(using Context): Tree =
var elems = checkWellFormedTupleElems(tree.trees)
if ctx.mode.is(Mode.Pattern) then elems = adaptPatternArgs(elems, pt)
val elemValues = elems.mapConserve(stripNamedArg)
val tup =
val arity = elems.length
if arity <= Definitions.MaxTupleArity then
def tupleTypeRef = defn.TupleType(arity).nn
val tree1 =
if arity == 0 then
if ctx.mode is Mode.Type then TypeTree(defn.UnitType) else unitLiteral
else if ctx.mode is Mode.Type then AppliedTypeTree(ref(tupleTypeRef), elemValues)
else Apply(ref(tupleTypeRef.classSymbol.companionModule.termRef), elemValues)
tree1.withSpan(tree.span)
else
cpy.Tuple(tree)(elemValues)
val names = elems.collect:
case NamedArg(name, arg) => name
if names.isEmpty || ctx.mode.is(Mode.Pattern) then
tup
else
def namesTuple = withModeBits(ctx.mode &~ Mode.Pattern | Mode.Type):
tuple(Tuple(
names.map: name =>
SingletonTypeTree(Literal(Constant(name.toString))).withSpan(tree.span)),
WildcardType)
if ctx.mode.is(Mode.Type) then
AppliedTypeTree(ref(defn.NamedTupleTypeRef), namesTuple :: tup :: Nil)
else
Apply(
Apply(
TypeApply(
Select(ref(defn.NamedTupleModule), nme.build), // NamedTuple.build
namesTuple :: Nil), // ++ [(names...)]
Nil), // ++ ()
tup :: Nil) // .++ ((values...))
/** When desugaring a list pattern arguments `elems` adapt them and the
* expected type `pt` to each other. This means:
* - If `elems` are named pattern elements, rearrange them to match `pt`.
* This requires all names in `elems` to be also present in `pt`.
*/
def adaptPatternArgs(elems: List[Tree], pt: Type)(using Context): List[Tree] =
def reorderedNamedArgs(wildcardSpan: Span): List[untpd.Tree] =
var selNames = pt.namedTupleElementTypes.map(_(0))
if selNames.isEmpty && pt.classSymbol.is(CaseClass) then
selNames = pt.classSymbol.caseAccessors.map(_.name.asTermName)
val nameToIdx = selNames.zipWithIndex.toMap
val reordered = Array.fill[untpd.Tree](selNames.length):
untpd.Ident(nme.WILDCARD).withSpan(wildcardSpan)
for case arg @ NamedArg(name: TermName, _) <- elems do
nameToIdx.get(name) match
case Some(idx) =>
if reordered(idx).isInstanceOf[Ident] then
reordered(idx) = arg
else
report.error(em"Duplicate named pattern", arg.srcPos)
case _ =>
report.error(em"No element named `$name` is defined in selector type $pt", arg.srcPos)
reordered.toList
elems match
case (first @ NamedArg(_, _)) :: _ => reorderedNamedArgs(first.span.startPos)
case _ => elems
end adaptPatternArgs
private def isTopLevelDef(stat: Tree)(using Context): Boolean = stat match
case _: ValDef | _: PatDef | _: DefDef | _: Export | _: ExtMethods => true
case stat: ModuleDef =>
stat.mods.isOneOf(GivenOrImplicit)
case stat: TypeDef =>
!stat.isClassDef || stat.mods.isOneOf(GivenOrImplicit)
case _ =>
false
/** Assuming `src` contains top-level definition, returns the name that should
* be using for the package object that will wrap them.
*/
def packageObjectName(src: SourceFile): TermName =
val fileName = src.file.name
val sourceName = fileName.take(fileName.lastIndexOf('.'))
(sourceName ++ str.TOPLEVEL_SUFFIX).toTermName
/** Group all definitions that can't be at the toplevel in
* an object named `
© 2015 - 2025 Weber Informatics LLC | Privacy Policy