diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 542ce8c080d5..42d24daf3255 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -999,6 +999,18 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => case _ => None case _ => None end AssertNotNull + + object ConstantValue { + def unapply(tree: Tree)(using Context): Option[Any] = + tree match + case Typed(expr, _) => unapply(expr) + case Inlined(_, Nil, expr) => unapply(expr) + case Block(Nil, expr) => unapply(expr) + case _ => + tree.tpe.widenTermRefExpr.normalized match + case ConstantType(Constant(x)) => Some(x) + case _ => None + } } object TreeInfo { diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 44446080c106..2f82c85ba9a0 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -14,7 +14,8 @@ import Uniques._ import ast.Trees._ import ast.untpd import util.{NoSource, SimpleIdentityMap, SourceFile, HashSet, ReusableInstance} -import typer.{Implicits, ImportInfo, Inliner, SearchHistory, SearchRoot, TypeAssigner, Typer, Nullables} +import typer.{Implicits, ImportInfo, SearchHistory, SearchRoot, TypeAssigner, Typer, Nullables} +import inlines.Inliner import Nullables._ import Implicits.ContextualImplicits import config.Settings._ diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 93e2e7f0c0d3..440871481114 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -830,7 +830,7 @@ class TreeUnpickler(reader: TastyReader, else if sym.isInlineMethod && !sym.is(Deferred) then // The body of an inline method is stored in an annotation, so no need to unpickle it again new Trees.Lazy[Tree] { - def complete(using Context) = typer.Inliner.bodyToInline(sym) + def complete(using Context) = inlines.Inlines.bodyToInline(sym) } else readLater(end, _.readTerm()) diff --git a/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala b/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala new file mode 100644 index 000000000000..7be63fc8ba3a --- /dev/null +++ b/compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala @@ -0,0 +1,430 @@ +package dotty.tools +package dotc +package inlines + +import ast.*, core.* +import Flags.*, Symbols.*, Types.*, Decorators.*, Contexts.* +import StdNames.nme +import transform.SymUtils.* +import typer.* +import Names.TermName +import NameKinds.{InlineAccessorName, InlineBinderName, InlineScrutineeName} +import config.Printers.inlining +import util.SimpleIdentityMap + +import collection.mutable + +/** A utility object offering methods for rewriting inlined code */ +class InlineReducer(inliner: Inliner)(using Context): + import tpd.* + import Inliner.{isElideableExpr, DefBuffer} + import inliner.{call, newSym, tryInlineArg, paramBindingDef} + + extension (tp: Type) + /** same as widenTermRefExpr, but preserves modules and singleton enum values */ + private def widenInlineScrutinee(using Context): Type = tp.stripTypeVar match + case tp: TermRef => + val sym = tp.termSymbol + if sym.isAllOf(EnumCase, butNot=JavaDefined) || sym.is(Module) then tp + else if !tp.isOverloaded then tp.underlying.widenExpr.widenInlineScrutinee + else tp + case _ => tp + + /** An extractor for terms equivalent to `new C(args)`, returning the class `C`, + * a list of bindings, and the arguments `args`. Can see inside blocks and Inlined nodes and can + * follow a reference to an inline value binding to its right hand side. + * + * @return optionally, a triple consisting of + * - the class `C` + * - the arguments `args` + * - any bindings that wrap the instance creation + * - whether the instance creation is precomputed or by-name + */ + private object NewInstance { + def unapply(tree: Tree)(using Context): Option[(Symbol, List[Tree], List[Tree], Boolean)] = { + def unapplyLet(bindings: List[Tree], expr: Tree) = + unapply(expr) map { + case (cls, reduced, prefix, precomputed) => (cls, reduced, bindings ::: prefix, precomputed) + } + tree match { + case Apply(fn, args) => + fn match { + case Select(New(tpt), nme.CONSTRUCTOR) => + Some((tpt.tpe.classSymbol, args, Nil, false)) + case TypeApply(Select(New(tpt), nme.CONSTRUCTOR), _) => + Some((tpt.tpe.classSymbol, args, Nil, false)) + case _ => + val meth = fn.symbol + if (meth.name == nme.apply && + meth.flags.is(Synthetic) && + meth.owner.linkedClass.is(Case)) + Some(meth.owner.linkedClass, args, Nil, false) + else None + } + case Typed(inner, _) => + // drop the ascribed tpt. We only need it if we can't find a NewInstance + unapply(inner) + case Ident(_) => + val binding = tree.symbol.defTree + for ((cls, reduced, prefix, precomputed) <- unapply(binding)) + yield (cls, reduced, prefix, precomputed || binding.isInstanceOf[ValDef]) + case Inlined(_, bindings, expansion) => + unapplyLet(bindings, expansion) + case Block(stats, expr) if isElideableExpr(tree) => + unapplyLet(stats, expr) + case _ => + None + } + } + } + + /** If `tree` is equivalent to `new C(args).x` where class `C` does not have + * initialization code and `x` is a parameter corresponding to one of the + * arguments `args`, the corresponding argument, otherwise `tree` itself. + * Side effects of original arguments need to be preserved. + */ + def reduceProjection(tree: Tree)(using Context): Tree = { + if (ctx.debug) inlining.println(i"try reduce projection $tree") + tree match { + case Select(NewInstance(cls, args, prefix, precomputed), field) if cls.isNoInitsRealClass => + def matches(param: Symbol, selection: Symbol): Boolean = + param == selection || { + selection.name match { + case InlineAccessorName(underlying) => + param.name == underlying && selection.info.isInstanceOf[ExprType] + case _ => + false + } + } + val idx = cls.asClass.paramAccessors.indexWhere(matches(_, tree.symbol)) + if (idx >= 0 && idx < args.length) { + def finish(arg: Tree) = + new TreeTypeMap().transform(arg) // make sure local bindings in argument have fresh symbols + .showing(i"projecting $tree -> $result", inlining) + val arg = args(idx) + if (precomputed) + if (isElideableExpr(arg)) finish(arg) + else tree // nothing we can do here, projection would duplicate side effect + else { + // newInstance is evaluated in place, need to reflect side effects of + // arguments in the order they were written originally + def collectImpure(from: Int, end: Int) = + (from until end).filterNot(i => isElideableExpr(args(i))).toList.map(args) + val leading = collectImpure(0, idx) + val trailing = collectImpure(idx + 1, args.length) + val argInPlace = + if (trailing.isEmpty) arg + else + def argsSpan = trailing.map(_.span).foldLeft(arg.span)(_.union(_)) + letBindUnless(TreeInfo.Pure, arg)(Block(trailing, _).withSpan(argsSpan)) + val blockSpan = (prefix ::: leading).map(_.span).foldLeft(argInPlace.span)(_.union(_)) + finish(seq(prefix, seq(leading, argInPlace)).withSpan(blockSpan)) + } + } + else tree + case Block(stats, expr) if stats.forall(isPureBinding) => + cpy.Block(tree)(stats, reduceProjection(expr)) + case _ => tree + } + } + + /** If this is a value binding: + * - reduce its rhs if it is a projection and adjust its type accordingly, + * - record symbol -> rhs in the InlineBindings context propery. + */ + def normalizeBinding(binding: ValOrDefDef)(using Context) = { + val binding1 = binding match { + case binding: ValDef => + val rhs1 = reduceProjection(binding.rhs) + binding.symbol.defTree = rhs1 + if (rhs1 `eq` binding.rhs) binding + else { + binding.symbol.info = rhs1.tpe + cpy.ValDef(binding)(tpt = TypeTree(rhs1.tpe), rhs = rhs1) + } + case _ => + binding + } + binding1.withSpan(call.span) + } + + /** Rewrite an application + * + * ((x1, ..., xn) => b)(e1, ..., en) + * + * to + * + * val/def x1 = e1; ...; val/def xn = en; b + * + * where `def` is used for call-by-name parameters. However, we shortcut any NoPrefix + * refs among the ei's directly without creating an intermediate binding. + */ + def betaReduce(tree: Tree)(using Context): Tree = tree match { + case Apply(Select(cl @ closureDef(ddef), nme.apply), args) if defn.isFunctionType(cl.tpe) => + // closureDef also returns a result for closures wrapped in Inlined nodes. + // These need to be preserved. + def recur(cl: Tree): Tree = cl match + case Inlined(call, bindings, expr) => + cpy.Inlined(cl)(call, bindings, recur(expr)) + case _ => ddef.tpe.widen match + case mt: MethodType if ddef.paramss.head.length == args.length => + val bindingsBuf = new DefBuffer + val argSyms = mt.paramNames.lazyZip(mt.paramInfos).lazyZip(args).map { (name, paramtp, arg) => + arg.tpe.dealias match { + case ref @ TermRef(NoPrefix, _) => ref.symbol + case _ => + paramBindingDef(name, paramtp, arg, bindingsBuf)( + using ctx.withSource(cl.source) + ).symbol + } + } + val expander = new TreeTypeMap( + oldOwners = ddef.symbol :: Nil, + newOwners = ctx.owner :: Nil, + substFrom = ddef.paramss.head.map(_.symbol), + substTo = argSyms) + Block(bindingsBuf.toList, expander.transform(ddef.rhs)).withSpan(tree.span) + case _ => tree + recur(cl) + case _ => tree + } + + /** The result type of reducing a match. It consists optionally of a list of bindings + * for the pattern-bound variables and the RHS of the selected case. + * Returns `None` if no case was selected. + */ + type MatchRedux = Option[(List[MemberDef], Tree)] + + /** Same as MatchRedux, but also includes a boolean + * that is true if the guard can be checked at compile time. + */ + type MatchReduxWithGuard = Option[(List[MemberDef], Tree, Boolean)] + + /** Reduce an inline match + * @param mtch the match tree + * @param scrutinee the scrutinee expression, assumed to be pure, or + * EmptyTree for a summonFrom + * @param scrutType its fully defined type, or + * ImplicitScrutineeTypeRef for a summonFrom + * @param typer The current inline typer + * @return optionally, if match can be reduced to a matching case: A pair of + * bindings for all pattern-bound variables and the RHS of the case. + */ + def reduceInlineMatch(scrutinee: Tree, scrutType: Type, cases: List[CaseDef], typer: Typer)(using Context): MatchRedux = { + + val isImplicit = scrutinee.isEmpty + + /** Try to match pattern `pat` against scrutinee reference `scrut`. If successful add + * bindings for variables bound in this pattern to `caseBindingMap`. + */ + def reducePattern( + caseBindingMap: mutable.ListBuffer[(Symbol, MemberDef)], + scrut: TermRef, + pat: Tree + )(using Context): Boolean = { + + /** Create a binding of a pattern bound variable with matching part of + * scrutinee as RHS and type that corresponds to RHS. + */ + def newTermBinding(sym: TermSymbol, rhs: Tree): Unit = { + val copied = sym.copy(info = rhs.tpe.widenInlineScrutinee, coord = sym.coord, flags = sym.flags &~ Case).asTerm + caseBindingMap += ((sym, ValDef(copied, constToLiteral(rhs)).withSpan(sym.span))) + } + + def newTypeBinding(sym: TypeSymbol, alias: Type): Unit = { + val copied = sym.copy(info = TypeAlias(alias), coord = sym.coord).asType + caseBindingMap += ((sym, TypeDef(copied))) + } + + def searchImplicit(sym: TermSymbol, tpt: Tree) = { + val evTyper = new Typer(ctx.nestingLevel + 1) + val evCtx = ctx.fresh.setTyper(evTyper) + inContext(evCtx) { + val evidence = evTyper.inferImplicitArg(tpt.tpe, tpt.span) + evidence.tpe match { + case fail: Implicits.AmbiguousImplicits => + report.error(evTyper.missingArgMsg(evidence, tpt.tpe, ""), tpt.srcPos) + true // hard error: return true to stop implicit search here + case fail: Implicits.SearchFailureType => + false + case _ => + //inlining.println(i"inferred implicit $sym: ${sym.info} with $evidence: ${evidence.tpe.widen}, ${evCtx.gadt.constraint}, ${evCtx.typerState.constraint}") + newTermBinding(sym, evidence) + true + } + } + } + + type TypeBindsMap = SimpleIdentityMap[TypeSymbol, java.lang.Boolean] + + def getTypeBindsMap(pat: Tree, tpt: Tree): TypeBindsMap = { + val getBinds = new TreeAccumulator[Set[TypeSymbol]] { + def apply(syms: Set[TypeSymbol], t: Tree)(using Context): Set[TypeSymbol] = { + val syms1 = t match { + case t: Bind if t.symbol.isType => + syms + t.symbol.asType + case _ => syms + } + foldOver(syms1, t) + } + } + + // Extractors contain Bind nodes in type parameter lists, the tree looks like this: + // UnApply[t @ t](pats)(implicits): T[t] + // Test case is pos/inline-caseclass.scala. + val binds: Set[TypeSymbol] = pat match { + case UnApply(TypeApply(_, tpts), _, _) => getBinds(Set.empty[TypeSymbol], tpts) + case _ => getBinds(Set.empty[TypeSymbol], tpt) + } + + val extractBindVariance = new TypeAccumulator[TypeBindsMap] { + def apply(syms: TypeBindsMap, t: Type) = { + val syms1 = t match { + // `binds` is used to check if the symbol was actually bound by the pattern we're processing + case tr: TypeRef if tr.symbol.is(Case) && binds.contains(tr.symbol.asType) => + val trSym = tr.symbol.asType + // Exact same logic as in IsFullyDefinedAccumulator: + // the binding is to be maximized iff it only occurs contravariantly in the type + val wasToBeMinimized: Boolean = { + val v = syms(trSym) + if (v != null) v else false + } + syms.updated(trSym, wasToBeMinimized || variance >= 0 : java.lang.Boolean) + case _ => + syms + } + foldOver(syms1, t) + } + } + + extractBindVariance(SimpleIdentityMap.empty, tpt.tpe) + } + + def addTypeBindings(typeBinds: TypeBindsMap)(using Context): Unit = + typeBinds.foreachBinding { case (sym, shouldBeMinimized) => + newTypeBinding(sym, ctx.gadt.approximation(sym, fromBelow = shouldBeMinimized)) + } + + def registerAsGadtSyms(typeBinds: TypeBindsMap)(using Context): Unit = + if (typeBinds.size > 0) ctx.gadt.addToConstraint(typeBinds.keys) + + pat match { + case Typed(pat1, tpt) => + val typeBinds = getTypeBindsMap(pat1, tpt) + registerAsGadtSyms(typeBinds) + scrut <:< tpt.tpe && { + addTypeBindings(typeBinds) + reducePattern(caseBindingMap, scrut, pat1) + } + case pat @ Bind(name: TermName, Typed(_, tpt)) if isImplicit => + val typeBinds = getTypeBindsMap(tpt, tpt) + registerAsGadtSyms(typeBinds) + searchImplicit(pat.symbol.asTerm, tpt) && { + addTypeBindings(typeBinds) + true + } + case pat @ Bind(name: TermName, body) => + reducePattern(caseBindingMap, scrut, body) && { + if (name != nme.WILDCARD) newTermBinding(pat.symbol.asTerm, ref(scrut)) + true + } + case Ident(nme.WILDCARD) => + true + case pat: Literal => + scrut.widenTermRefExpr =:= pat.tpe + case pat: RefTree => + scrut =:= pat.tpe || + scrut.classSymbol.is(Module) && scrut.widen =:= pat.tpe.widen && { + scrut.prefix match { + case _: SingletonType | NoPrefix => true + case _ => false + } + } + case UnApply(unapp, _, pats) => + unapp.tpe.widen match { + case mt: MethodType if mt.paramInfos.length == 1 => + + def reduceSubPatterns(pats: List[Tree], selectors: List[Tree]): Boolean = (pats, selectors) match { + case (Nil, Nil) => true + case (pat :: pats1, selector :: selectors1) => + val elem = newSym(InlineBinderName.fresh(), Synthetic, selector.tpe.widenInlineScrutinee).asTerm + val rhs = constToLiteral(selector) + elem.defTree = rhs + caseBindingMap += ((NoSymbol, ValDef(elem, rhs).withSpan(elem.span))) + reducePattern(caseBindingMap, elem.termRef, pat) && + reduceSubPatterns(pats1, selectors1) + case _ => false + } + + val paramType = mt.paramInfos.head + val paramCls = paramType.classSymbol + if (paramCls.is(Case) && unapp.symbol.is(Synthetic) && scrut <:< paramType) { + val caseAccessors = + if (paramCls.is(Scala2x)) paramCls.caseAccessors.filter(_.is(Method)) + else paramCls.asClass.paramAccessors + val selectors = + for (accessor <- caseAccessors) + yield constToLiteral(reduceProjection(ref(scrut).select(accessor).ensureApplied)) + caseAccessors.length == pats.length && reduceSubPatterns(pats, selectors) + } + else false + case _ => + false + } + case Alternative(pats) => + pats.exists(reducePattern(caseBindingMap, scrut, _)) + case Inlined(EmptyTree, Nil, ipat) => + reducePattern(caseBindingMap, scrut, ipat) + case _ => false + } + } + + /** The initial scrutinee binding: `val $scrutineeN = ` */ + val scrutineeSym = newSym(InlineScrutineeName.fresh(), Synthetic, scrutType).asTerm + val scrutineeBinding = normalizeBinding(ValDef(scrutineeSym, scrutinee)) + + def reduceCase(cdef: CaseDef): MatchReduxWithGuard = { + val caseBindingMap = new mutable.ListBuffer[(Symbol, MemberDef)]() + + def substBindings( + bindings: List[(Symbol, MemberDef)], + bbuf: mutable.ListBuffer[MemberDef], + from: List[Symbol], to: List[Symbol]): (List[MemberDef], List[Symbol], List[Symbol]) = + bindings match { + case (sym, binding) :: rest => + bbuf += binding.subst(from, to).asInstanceOf[MemberDef] + if (sym.exists) substBindings(rest, bbuf, sym :: from, binding.symbol :: to) + else substBindings(rest, bbuf, from, to) + case Nil => (bbuf.toList, from, to) + } + + if (!isImplicit) caseBindingMap += ((NoSymbol, scrutineeBinding)) + val gadtCtx = ctx.fresh.setFreshGADTBounds.addMode(Mode.GadtConstraintInference) + if (reducePattern(caseBindingMap, scrutineeSym.termRef, cdef.pat)(using gadtCtx)) { + val (caseBindings, from, to) = substBindings(caseBindingMap.toList, mutable.ListBuffer(), Nil, Nil) + val (guardOK, canReduceGuard) = + if cdef.guard.isEmpty then (true, true) + else typer.typed(cdef.guard.subst(from, to), defn.BooleanType) match { + case ConstantValue(v: Boolean) => (v, true) + case _ => (false, false) + } + if guardOK then Some((caseBindings.map(_.subst(from, to)), cdef.body.subst(from, to), canReduceGuard)) + else if canReduceGuard then None + else Some((caseBindings.map(_.subst(from, to)), cdef.body.subst(from, to), canReduceGuard)) + } + else None + } + + def recur(cases: List[CaseDef]): MatchRedux = cases match { + case Nil => None + case cdef :: cases1 => + reduceCase(cdef) match + case None => recur(cases1) + case r @ Some((caseBindings, rhs, canReduceGuard)) if canReduceGuard => Some((caseBindings, rhs)) + case _ => None + } + + recur(cases) + } +end InlineReducer + diff --git a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala new file mode 100644 index 000000000000..a12a2d7fcac5 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala @@ -0,0 +1,1069 @@ +package dotty.tools +package dotc +package inlines + +import ast.*, core.* +import Flags.*, Symbols.*, Types.*, Decorators.*, Constants.*, Contexts.* +import transform.SymUtils.* +import StdNames.nme +import typer.* +import Names.Name +import NameKinds.InlineBinderName +import ProtoTypes.shallowSelectionProto +import SymDenotations.SymDenotation +import Inferencing.isFullyDefined +import config.Printers.inlining +import ErrorReporting.errorTree +import util.{SimpleIdentitySet, SrcPos} +import Nullables.computeNullableDeeply + +import collection.mutable +import reporting.trace +import util.Spans.Span +import dotty.tools.dotc.transform.Splicer +import quoted.QuoteUtils +import scala.annotation.constructorOnly + +/** General support for inlining */ +object Inliner: + import tpd._ + + private[inlines] type DefBuffer = mutable.ListBuffer[ValOrDefDef] + + /** Very similar to TreeInfo.isPureExpr, but with the following inliner-only exceptions: + * - synthetic case class apply methods, when the case class constructor is empty, are + * elideable but not pure. Elsewhere, accessing the apply method might cause the initialization + * of a containing object so they are merely idempotent. + */ + object isElideableExpr: + def isStatElideable(tree: Tree)(using Context): Boolean = unsplice(tree) match { + case EmptyTree + | TypeDef(_, _) + | Import(_, _) + | DefDef(_, _, _, _) => + true + case vdef @ ValDef(_, _, _) => + if (vdef.symbol.flags is Mutable) false else apply(vdef.rhs) + case _ => + false + } + + def apply(tree: Tree)(using Context): Boolean = unsplice(tree) match { + case EmptyTree + | This(_) + | Super(_, _) + | Literal(_) => + true + case Ident(_) => + isPureRef(tree) || tree.symbol.isAllOf(InlineParam) + case Select(qual, _) => + if (tree.symbol.is(Erased)) true + else isPureRef(tree) && apply(qual) + case New(_) | Closure(_, _, _) => + true + case TypeApply(fn, _) => + if (fn.symbol.is(Erased) || fn.symbol == defn.QuotedTypeModule_of) true else apply(fn) + case Apply(fn, args) => + val isCaseClassApply = { + val cls = tree.tpe.classSymbol + val meth = fn.symbol + meth.name == nme.apply && + meth.flags.is(Synthetic) && + meth.owner.linkedClass.is(Case) && + cls.isNoInitsRealClass && + funPart(fn).match + case Select(qual, _) => qual.symbol.is(Synthetic) // e.g: disallow `{ ..; Foo }.apply(..)` + case meth @ Ident(_) => meth.symbol.is(Synthetic) // e.g: allow `import Foo.{ apply => foo }; foo(..)` + case _ => false + } + if isPureApply(tree, fn) then + apply(fn) && args.forall(apply) + else if (isCaseClassApply) + args.forall(apply) + else if (fn.symbol.is(Erased)) true + else false + case Typed(expr, _) => + apply(expr) + case Block(stats, expr) => + apply(expr) && stats.forall(isStatElideable) + case Inlined(_, bindings, expr) => + apply(expr) && bindings.forall(isStatElideable) + case NamedArg(_, expr) => + apply(expr) + case _ => + false + } + end isElideableExpr + + // InlineCopier is a more fault-tolerant copier that does not cause errors when + // function types in applications are undefined. This is necessary since we copy at + // the same time as establishing the proper context in which the copied tree should + // be evaluated. This matters for opaque types, see neg/i14653.scala. + private class InlineCopier() extends TypedTreeCopier: + override def Apply(tree: Tree)(fun: Tree, args: List[Tree])(using Context): Apply = + if fun.tpe.widen.exists then super.Apply(tree)(fun, args) + else untpd.cpy.Apply(tree)(fun, args).withTypeUnchecked(tree.tpe) + + // InlinerMap is a TreeTypeMap with special treatment for inlined arguments: + // They are generally left alone (not mapped further, and if they wrap a type + // the type Inlined wrapper gets dropped + private class InlinerMap( + typeMap: Type => Type, + treeMap: Tree => Tree, + oldOwners: List[Symbol], + newOwners: List[Symbol], + substFrom: List[Symbol], + substTo: List[Symbol])(using Context) + extends TreeTypeMap( + typeMap, treeMap, oldOwners, newOwners, substFrom, substTo, InlineCopier()): + + override def copy( + typeMap: Type => Type, + treeMap: Tree => Tree, + oldOwners: List[Symbol], + newOwners: List[Symbol], + substFrom: List[Symbol], + substTo: List[Symbol])(using Context) = + new InlinerMap(typeMap, treeMap, oldOwners, newOwners, substFrom, substTo) + + override def transformInlined(tree: Inlined)(using Context) = + if tree.call.isEmpty then + tree.expansion match + case expansion: TypeTree => expansion + case _ => tree + else super.transformInlined(tree) + end InlinerMap +end Inliner + +/** Produces an inlined version of `call` via its `inlined` method. + * + * @param call the original call to an inlineable method + * @param rhsToInline the body of the inlineable method that replaces the call. + */ +class Inliner(val call: tpd.Tree)(using Context): + import tpd._ + import Inliner._ + + private val methPart = funPart(call) + protected val callTypeArgs = typeArgss(call).flatten + protected val callValueArgss = termArgss(call) + protected val inlinedMethod = methPart.symbol + private val inlineCallPrefix = + qualifier(methPart).orElse(This(inlinedMethod.enclosingClass.asClass)) + + // Make sure all type arguments to the call are fully determined, + // but continue if that's not achievable (or else i7459.scala would crash). + for arg <- callTypeArgs do + isFullyDefined(arg.tpe, ForceDegree.flipBottom) + + /** A map from parameter names of the inlineable method to references of the actual arguments. + * For a type argument this is the full argument type. + * For a value argument, it is a reference to either the argument value + * (if the argument is a pure expression of singleton type), or to `val` or `def` acting + * as a proxy (if the argument is something else). + */ + private val paramBinding = new mutable.HashMap[Name, Type] + + /** A map from parameter names of the inlineable method to spans of the actual arguments */ + private val paramSpan = new mutable.HashMap[Name, Span] + + /** A map from references to (type and value) parameters of the inlineable method + * to their corresponding argument or proxy references, as given by `paramBinding`. + */ + private[inlines] val paramProxy = new mutable.HashMap[Type, Type] + + /** A map from the classes of (direct and outer) this references in `rhsToInline` + * to references of their proxies. + * Note that we can't index by the ThisType itself since there are several + * possible forms to express what is logicaly the same ThisType. E.g. + * + * ThisType(TypeRef(ThisType(p), cls)) + * + * vs + * + * ThisType(TypeRef(TermRef(ThisType(), p), cls)) + * + * These are different (wrt ==) types but represent logically the same key + */ + private val thisProxy = new mutable.HashMap[ClassSymbol, TermRef] + + /** A buffer for bindings that define proxies for actual arguments */ + private val bindingsBuf = new mutable.ListBuffer[ValOrDefDef] + + private[inlines] def newSym(name: Name, flags: FlagSet, info: Type)(using Context): Symbol = + newSymbol(ctx.owner, name, flags, info, coord = call.span) + + /** A binding for the parameter of an inline method. This is a `val` def for + * by-value parameters and a `def` def for by-name parameters. `val` defs inherit + * inline annotations from their parameters. The generated `def` is appended + * to `buf`. + * @param name the name of the parameter + * @param formal the type of the parameter + * @param arg the argument corresponding to the parameter + * @param buf the buffer to which the definition should be appended + */ + private[inlines] def paramBindingDef(name: Name, formal: Type, arg0: Tree, + buf: DefBuffer)(using Context): ValOrDefDef = { + val isByName = formal.dealias.isInstanceOf[ExprType] + val arg = arg0 match { + case Typed(arg1, tpt) if tpt.tpe.isRepeatedParam && arg1.tpe.derivesFrom(defn.ArrayClass) => + wrapArray(arg1, arg0.tpe.elemType) + case _ => arg0 + } + val argtpe = arg.tpe.dealiasKeepAnnots.translateFromRepeated(toArray = false) + val argIsBottom = argtpe.isBottomTypeAfterErasure + val bindingType = + if argIsBottom then formal + else if isByName then ExprType(argtpe.widen) + else argtpe.widen + var bindingFlags: FlagSet = InlineProxy + if formal.widenExpr.hasAnnotation(defn.InlineParamAnnot) then + bindingFlags |= Inline + if formal.widenExpr.hasAnnotation(defn.ErasedParamAnnot) then + bindingFlags |= Erased + if isByName then + bindingFlags |= Method + val boundSym = newSym(InlineBinderName.fresh(name.asTermName), bindingFlags, bindingType).asTerm + val binding = { + var newArg = arg.changeOwner(ctx.owner, boundSym) + if bindingFlags.is(Inline) && argIsBottom then + newArg = Typed(newArg, TypeTree(formal)) // type ascribe RHS to avoid type errors in expansion. See i8612.scala + if isByName then DefDef(boundSym, newArg) + else ValDef(boundSym, newArg) + }.withSpan(boundSym.span) + inlining.println(i"parameter binding: $binding, $argIsBottom") + buf += binding + binding + } + + /** Populate `paramBinding` and `buf` by matching parameters with + * corresponding arguments. `bindingbuf` will be further extended later by + * proxies to this-references. Issue an error if some arguments are missing. + */ + private def computeParamBindings( + tp: Type, targs: List[Tree], + argss: List[List[Tree]], formalss: List[List[Type]], + buf: DefBuffer): Boolean = + tp match + case tp: PolyType => + tp.paramNames.lazyZip(targs).foreach { (name, arg) => + paramSpan(name) = arg.span + paramBinding(name) = arg.tpe.stripTypeVar + } + computeParamBindings(tp.resultType, targs.drop(tp.paramNames.length), argss, formalss, buf) + case tp: MethodType => + if argss.isEmpty then + report.error(i"missing arguments for inline method $inlinedMethod", call.srcPos) + false + else + tp.paramNames.lazyZip(formalss.head).lazyZip(argss.head).foreach { (name, formal, arg) => + paramSpan(name) = arg.span + paramBinding(name) = arg.tpe.dealias match + case _: SingletonType if isIdempotentPath(arg) => + arg.tpe + case _ => + paramBindingDef(name, formal, arg, buf).symbol.termRef + } + computeParamBindings(tp.resultType, targs, argss.tail, formalss.tail, buf) + case _ => + assert(targs.isEmpty) + assert(argss.isEmpty) + true + + // Compute val-definitions for all this-proxies and append them to `bindingsBuf` + private def computeThisBindings() = { + // All needed this-proxies, paired-with and sorted-by nesting depth of + // the classes they represent (innermost first) + val sortedProxies = thisProxy.toList + .map((cls, proxy) => (cls.ownersIterator.length, proxy.symbol)) + .sortBy(-_._1) + + var lastSelf: Symbol = NoSymbol + var lastLevel: Int = 0 + for ((level, selfSym) <- sortedProxies) { + val rhs = selfSym.info.dealias match + case info: TermRef + if info.isStable && (lastSelf.exists || isPureExpr(inlineCallPrefix)) => + // If this is the first proxy, optimize to `ref(info)` only if call prefix is pure. + // Otherwise we might forget side effects. See run/i12829.scala. + ref(info) + case info => + val rhsClsSym = info.widenDealias.classSymbol + if rhsClsSym.is(Module) && rhsClsSym.isStatic then + ref(rhsClsSym.sourceModule) + else if lastSelf.exists then + ref(lastSelf).outerSelect(lastLevel - level, selfSym.info) + else + val pre = inlineCallPrefix match + case Super(qual, _) => qual + case pre => pre + val preLevel = inlinedMethod.owner.ownersIterator.length + if preLevel > level then pre.outerSelect(preLevel - level, selfSym.info) + else pre + + val binding = accountForOpaques( + ValDef(selfSym.asTerm, QuoteUtils.changeOwnerOfTree(rhs, selfSym)).withSpan(selfSym.span)) + bindingsBuf += binding + inlining.println(i"proxy at $level: $selfSym = ${bindingsBuf.last}") + lastSelf = selfSym + lastLevel = level + } + } + + /** A list of pairs between TermRefs appearing in thisProxy bindings that + * refer to objects with opaque type aliases and local proxy symbols + * that contain refined versions of these TermRefs where the aliases + * are exposed. + */ + private val opaqueProxies = new mutable.ListBuffer[(TermRef, TermRef)] + + protected def hasOpaqueProxies = opaqueProxies.nonEmpty + + /** Map first halfs of opaqueProxies pairs to second halfs, using =:= as equality */ + private def mapRef(ref: TermRef): Option[TermRef] = + opaqueProxies.collectFirst { + case (from, to) if from.symbol == ref.symbol && from =:= ref => to + } + + /** If `tp` contains TermRefs that refer to objects with opaque + * type aliases, add proxy definitions to `opaqueProxies` that expose these aliases. + */ + private def addOpaqueProxies(tp: Type, span: Span, forThisProxy: Boolean)(using Context): Unit = + tp.foreachPart { + case ref: TermRef => + for cls <- ref.widen.baseClasses do + if cls.containsOpaques + && (forThisProxy || inlinedMethod.isContainedIn(cls)) + && mapRef(ref).isEmpty + then + def openOpaqueAliases(selfType: Type): List[(Name, Type)] = selfType match + case RefinedType(parent, rname, TypeAlias(alias)) => + val opaq = cls.info.member(rname).symbol + if opaq.isOpaqueAlias then + (rname, alias.stripLazyRef.asSeenFrom(ref, cls)) + :: openOpaqueAliases(parent) + else Nil + case _ => + Nil + val refinements = openOpaqueAliases(cls.givenSelfType) + val refinedType = refinements.foldLeft(ref: Type) ((parent, refinement) => + RefinedType(parent, refinement._1, TypeAlias(refinement._2)) + ) + val refiningSym = newSym(InlineBinderName.fresh(), Synthetic, refinedType).asTerm + val refiningDef = ValDef(refiningSym, tpd.ref(ref).cast(refinedType)).withSpan(span) + inlining.println(i"add opaque alias proxy $refiningDef for $ref in $tp") + bindingsBuf += refiningDef + opaqueProxies += ((ref, refiningSym.termRef)) + case _ => + } + + /** Map all TermRefs that match left element in `opaqueProxies` to the + * corresponding right element. + */ + private val mapOpaques = TreeTypeMap( + typeMap = new TypeMap: + override def stopAt = StopAt.Package + def apply(t: Type) = mapOver { + t match + case ref: TermRef => mapRef(ref).getOrElse(ref) + case _ => t + } + ) + + /** If `binding` contains TermRefs that refer to objects with opaque + * type aliases, add proxy definitions that expose these aliases + * and substitute such TermRefs with theproxies. Example from pos/opaque-inline1.scala: + * + * object refined: + * opaque type Positive = Int + * inline def Positive(value: Int): Positive = f(value) + * def f(x: Positive): Positive = x + * def run: Unit = { val x = 9; val nine = refined.Positive(x) } + * + * This generates the following proxies: + * + * val $proxy1: refined.type{type Positive = Int} = + * refined.$asInstanceOf$[refined.type{type Positive = Int}] + * val refined$_this: ($proxy1 : refined.type{Positive = Int}) = + * $proxy1 + * + * and every reference to `refined` in the inlined expression is replaced by + * `refined_$this`. + */ + private def accountForOpaques(binding: ValDef)(using Context): ValDef = + addOpaqueProxies(binding.symbol.info, binding.span, forThisProxy = true) + if opaqueProxies.isEmpty then binding + else + binding.symbol.info = mapOpaques.typeMap(binding.symbol.info) + mapOpaques.transform(binding).asInstanceOf[ValDef] + .showing(i"transformed this binding exposing opaque aliases: $result", inlining) + end accountForOpaques + + /** If value argument contains references to objects that contain opaque types, + * map them to their opaque proxies. + */ + private def mapOpaquesInValueArg(arg: Tree)(using Context): Tree = + val argType = arg.tpe.widenDealias + addOpaqueProxies(argType, arg.span, forThisProxy = false) + if opaqueProxies.nonEmpty then + val mappedType = mapOpaques.typeMap(argType) + if mappedType ne argType then arg.cast(AndType(arg.tpe, mappedType)) + else arg + else arg + + private def canElideThis(tpe: ThisType): Boolean = + inlineCallPrefix.tpe == tpe && ctx.owner.isContainedIn(tpe.cls) + || tpe.cls.isContainedIn(inlinedMethod) + || tpe.cls.is(Package) + || tpe.cls.isStaticOwner && !(tpe.cls.seesOpaques && inlinedMethod.isContainedIn(tpe.cls)) + + /** Populate `thisProxy` and `paramProxy` as follows: + * + * 1a. If given type refers to a static this, thisProxy binds it to corresponding global reference, + * 1b. If given type refers to an instance this to a class that is not contained in the + * inline method, create a proxy symbol and bind the thistype to refer to the proxy. + * The proxy is not yet entered in `bindingsBuf`; that will come later. + * 2. If given type refers to a parameter, make `paramProxy` refer to the entry stored + * in `paramNames` under the parameter's name. This roundabout way to bind parameter + * references to proxies is done because we don't know a priori what the parameter + * references of a method are (we only know the method's type, but that contains TypeParamRefs + * and MethodParams, not TypeRefs or TermRefs. + */ + private def registerType(tpe: Type): Unit = tpe match { + case tpe: ThisType if !canElideThis(tpe) && !thisProxy.contains(tpe.cls) => + val proxyName = s"${tpe.cls.name}_this".toTermName + def adaptToPrefix(tp: Type) = tp.asSeenFrom(inlineCallPrefix.tpe, inlinedMethod.owner) + val proxyType = inlineCallPrefix.tpe.dealias.tryNormalize match { + case typeMatchResult if typeMatchResult.exists => typeMatchResult + case _ => adaptToPrefix(tpe).widenIfUnstable + } + thisProxy(tpe.cls) = newSym(proxyName, InlineProxy, proxyType).termRef + if (!tpe.cls.isStaticOwner) + registerType(inlinedMethod.owner.thisType) // make sure we have a base from which to outer-select + for (param <- tpe.cls.typeParams) + paramProxy(param.typeRef) = adaptToPrefix(param.typeRef) + case tpe: NamedType + if tpe.symbol.is(Param) + && tpe.symbol.owner == inlinedMethod + && (tpe.symbol.isTerm || inlinedMethod.paramSymss.exists(_.contains(tpe.symbol))) + // this test is needed to rule out nested LambdaTypeTree parameters + // with the same name as the method's parameters. Note that the nested + // LambdaTypeTree parameters also have the inlineMethod as owner. C.f. i13460.scala. + && !paramProxy.contains(tpe) => + paramBinding.get(tpe.name) match + case Some(bound) => paramProxy(tpe) = bound + case _ => // can happen for params bound by type-lambda trees. + + // The widened type may contain param types too (see tests/pos/i12379a.scala) + if tpe.isTerm then registerType(tpe.widenTermRefExpr) + case _ => + } + + private val registerTypes = new TypeTraverser: + override def stopAt = StopAt.Package + override def traverse(t: Type) = + registerType(t) + traverseChildren(t) + + /** Register type of leaf node */ + private def registerLeaf(tree: Tree): Unit = tree match + case _: This | _: Ident | _: TypeTree => registerTypes.traverse(tree.typeOpt) + case _ => + + /** Make `tree` part of inlined expansion. This means its owner has to be changed + * from its `originalOwner`, and, if it comes from outside the inlined method + * itself, it has to be marked as an inlined argument. + */ + private def integrate(tree: Tree, originalOwner: Symbol)(using Context): Tree = + // assertAllPositioned(tree) // debug + tree.changeOwner(originalOwner, ctx.owner) + + def tryConstValue: Tree = + TypeComparer.constValue(callTypeArgs.head.tpe) match { + case Some(c) => Literal(c).withSpan(call.span) + case _ => EmptyTree + } + + val reducer = new InlineReducer(this) + + /** The Inlined node representing the inlined call */ + def inlined(rhsToInline: tpd.Tree): (List[MemberDef], Tree) = + + inlining.println(i"-----------------------\nInlining $call\nWith RHS $rhsToInline") + + def paramTypess(call: Tree, acc: List[List[Type]]): List[List[Type]] = call match + case Apply(fn, args) => + fn.tpe.widen.match + case mt: MethodType => paramTypess(fn, mt.instantiateParamInfos(args.tpes) :: acc) + case _ => Nil + case TypeApply(fn, _) => paramTypess(fn, acc) + case _ => acc + + val paramBindings = + val mappedCallValueArgss = callValueArgss.nestedMapConserve(mapOpaquesInValueArg) + if mappedCallValueArgss ne callValueArgss then + inlining.println(i"mapped value args = ${mappedCallValueArgss.flatten}%, %") + + val paramBindingsBuf = new DefBuffer + // Compute bindings for all parameters, appending them to bindingsBuf + if !computeParamBindings( + inlinedMethod.info, callTypeArgs, + mappedCallValueArgss, paramTypess(call, Nil), + paramBindingsBuf) + then + return (Nil, EmptyTree) + + paramBindingsBuf.toList + end paramBindings + + // make sure prefix is executed if it is impure + if !isIdempotentExpr(inlineCallPrefix) then registerType(inlinedMethod.owner.thisType) + + // Register types of all leaves of inlined body so that the `paramProxy` and `thisProxy` maps are defined. + rhsToInline.foreachSubTree(registerLeaf) + + // Compute bindings for all this-proxies, appending them to bindingsBuf + computeThisBindings() + + // Parameter bindings come after this bindings, reflecting order of evaluation + bindingsBuf ++= paramBindings + + val inlineTyper = new InlineTyper(ctx.reporter.errorCount) + + val inlineCtx = inlineContext(call).fresh.setTyper(inlineTyper).setNewScope + + def inlinedFromOutside(tree: Tree)(span: Span): Tree = + Inlined(EmptyTree, Nil, tree)(using ctx.withSource(inlinedMethod.topLevelClass.source)).withSpan(span) + + // A tree type map to prepare the inlined body for typechecked. + // The translation maps references to `this` and parameters to + // corresponding arguments or proxies on the type and term level. It also changes + // the owner from the inlined method to the current owner. + val inliner = new InlinerMap( + typeMap = + new DeepTypeMap { + override def stopAt = + if opaqueProxies.isEmpty then StopAt.Static else StopAt.Package + def apply(t: Type) = t match { + case t: ThisType => thisProxy.getOrElse(t.cls, t) + case t: TypeRef => paramProxy.getOrElse(t, mapOver(t)) + case t: SingletonType => + if t.termSymbol.isAllOf(InlineParam) then apply(t.widenTermRefExpr) + else paramProxy.getOrElse(t, mapOver(t)) + case t => mapOver(t) + } + }, + treeMap = { + case tree: This => + tree.tpe match { + case thistpe: ThisType => + thisProxy.get(thistpe.cls) match { + case Some(t) => + val thisRef = ref(t).withSpan(call.span) + inlinedFromOutside(thisRef)(tree.span) + case None => tree + } + case _ => tree + } + case tree: Ident => + /* Span of the argument. Used when the argument is inlined directly without a binding */ + def argSpan = + if (tree.name == nme.WILDCARD) tree.span // From type match + else if (tree.symbol.isTypeParam && tree.symbol.owner.isClass) tree.span // TODO is this the correct span? + else paramSpan(tree.name) + val inlinedCtx = ctx.withSource(inlinedMethod.topLevelClass.source) + paramProxy.get(tree.tpe) match { + case Some(t) if tree.isTerm && t.isSingleton => + val inlinedSingleton = singleton(t).withSpan(argSpan) + inlinedFromOutside(inlinedSingleton)(tree.span) + case Some(t) if tree.isType => + inlinedFromOutside(TypeTree(t).withSpan(argSpan))(tree.span) + case _ => tree + } + case tree @ Select(qual: This, name) if tree.symbol.is(Private) && tree.symbol.isInlineMethod => + // This inline method refers to another (private) inline method (see tests/pos/i14042.scala). + // We insert upcast to access the private inline method once inlined. This makes the selection + // keep the symbol when re-typechecking in the InlineTyper. The method is inlined and hence no + // reference to a private method is kept at runtime. + cpy.Select(tree)(qual.asInstance(qual.tpe.widen), name) + + case tree => tree + }, + oldOwners = inlinedMethod :: Nil, + newOwners = ctx.owner :: Nil, + substFrom = Nil, + substTo = Nil + )(using inlineCtx) + + inlining.println( + i"""inliner transform with + |thisProxy = ${thisProxy.toList.map(_._1)}%, % --> ${thisProxy.toList.map(_._2)}%, % + |paramProxy = ${paramProxy.toList.map(_._1.typeSymbol.showLocated)}%, % --> ${paramProxy.toList.map(_._2)}%, %""") + + // Apply inliner to `rhsToInline`, split off any implicit bindings from result, and + // make them part of `bindingsBuf`. The expansion is then the tree that remains. + val expansion = inliner.transform(rhsToInline) + + def issueError() = callValueArgss match { + case (msgArg :: Nil) :: Nil => + val message = msgArg.tpe match { + case ConstantType(Constant(msg: String)) => msg + case _ => s"A literal string is expected as an argument to `compiletime.error`. Got ${msgArg.show}" + } + // Usually `error` is called from within a rewrite method. In this + // case we need to report the error at the point of the outermost enclosing inline + // call. This way, a defensively written rewrite method can always + // report bad inputs at the point of call instead of revealing its internals. + val callToReport = if (enclosingInlineds.nonEmpty) enclosingInlineds.last else call + val ctxToReport = ctx.outersIterator.dropWhile(enclosingInlineds(using _).nonEmpty).next + // The context in which we report should still use the existing context reporter + val ctxOrigReporter = ctxToReport.fresh.setReporter(ctx.reporter) + inContext(ctxOrigReporter) { + report.error(message, callToReport.srcPos) + } + case _ => + } + + /** The number of nodes in this tree, excluding code in nested inline + * calls and annotations of definitions. + */ + def treeSize(x: Any): Int = + var siz = 0 + x match + case x: Trees.Inlined[_] => + case x: Positioned => + var i = 0 + while i < x.productArity do + siz += treeSize(x.productElement(i)) + i += 1 + case x: List[_] => + var xs = x + while xs.nonEmpty do + siz += treeSize(xs.head) + xs = xs.tail + case _ => + siz + + trace(i"inlining $call", inlining, show = true) { + + // The normalized bindings collected in `bindingsBuf` + bindingsBuf.mapInPlace { binding => + // Set trees to symbols allow macros to see the definition tree. + // This is used by `underlyingArgument`. + val binding1 = reducer.normalizeBinding(binding)(using inlineCtx).setDefTree + binding1.foreachSubTree { + case tree: MemberDef => tree.setDefTree + case _ => + } + binding1 + } + + // Run a typing pass over the inlined tree. See InlineTyper for details. + val expansion1 = inlineTyper.typed(expansion)(using inlineCtx) + + if (ctx.settings.verbose.value) { + inlining.println(i"to inline = $rhsToInline") + inlining.println(i"original bindings = ${bindingsBuf.toList}%\n%") + inlining.println(i"original expansion = $expansion1") + } + + // Drop unused bindings + val (finalBindings, finalExpansion) = dropUnusedDefs(bindingsBuf.toList, expansion1) + + if (inlinedMethod == defn.Compiletime_error) issueError() + + addInlinedTrees(treeSize(finalExpansion)) + + (finalBindings, finalExpansion) + } + end inlined + + /** An extractor for references to inlineable arguments. These are : + * - by-value arguments marked with `inline` + * - all by-name arguments + */ + private object InlineableArg { + lazy val paramProxies = paramProxy.values.toSet + def unapply(tree: Trees.Ident[?])(using Context): Option[Tree] = { + def search(buf: DefBuffer) = buf.find(_.name == tree.name) + if (paramProxies.contains(tree.typeOpt)) + search(bindingsBuf) match { + case Some(bind: ValOrDefDef) if bind.symbol.is(Inline) => + Some(integrate(bind.rhs, bind.symbol)) + case _ => None + } + else None + } + } + + private[inlines] def tryInlineArg(tree: Tree)(using Context): Tree = tree match { + case InlineableArg(rhs) => + inlining.println(i"inline arg $tree -> $rhs") + rhs + case _ => + EmptyTree + } + + /** A typer for inlined bodies. Beyond standard typing, an inline typer performs + * the following functions: + * + * 1. Implement constant folding over inlined code + * 2. Selectively expand ifs with constant conditions + * 3. Inline arguments that are by-name closures + * 4. Make sure inlined code is type-correct. + * 5. Make sure that the tree's typing is idempotent (so that future -Ycheck passes succeed) + */ + class InlineTyper(initialErrorCount: Int, @constructorOnly nestingLevel: Int = ctx.nestingLevel + 1) + extends ReTyper(nestingLevel): + import reducer._ + + override def ensureAccessible(tpe: Type, superAccess: Boolean, pos: SrcPos)(using Context): Type = { + tpe match { + case tpe: NamedType if tpe.symbol.exists && !tpe.symbol.isAccessibleFrom(tpe.prefix, superAccess) => + tpe.info match { + case TypeAlias(alias) => return ensureAccessible(alias, superAccess, pos) + case info: ConstantType if tpe.symbol.isStableMember => return info + case _ => + } + case _ => + } + super.ensureAccessible(tpe, superAccess, pos) + } + + /** Enter implicits in scope so that they can be found in implicit search. + * This is important for non-transparent inlines + */ + override def index(trees: List[untpd.Tree])(using Context): Context = + for case tree: untpd.MemberDef <- trees do + if tree.symbol.isOneOf(Flags.GivenOrImplicit) then + ctx.scope.openForMutations.enter(tree.symbol) + ctx + + override def typedIdent(tree: untpd.Ident, pt: Type)(using Context): Tree = + val tree1 = inlineIfNeeded( + tryInlineArg(tree.asInstanceOf[tpd.Tree]) `orElse` super.typedIdent(tree, pt) + ) + tree1 match + case id: Ident if tpd.needsSelect(id.tpe) => + inlining.println(i"expanding $id to selection") + ref(id.tpe.asInstanceOf[TermRef]).withSpan(id.span) + case _ => + tree1 + + override def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = { + val qual1 = typed(tree.qualifier, shallowSelectionProto(tree.name, pt, this)) + val resNoReduce = untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt) + val reducedProjection = reducer.reduceProjection(resNoReduce) + if reducedProjection.isType then + //if the projection leads to a typed tree then we stop reduction + resNoReduce + else + val res = constToLiteral(reducedProjection) + if resNoReduce ne res then + typed(res, pt) // redo typecheck if reduction changed something + else if res.symbol.isInlineMethod then + inlineIfNeeded(res) + else + ensureAccessible(res.tpe, tree.qualifier.isInstanceOf[untpd.Super], tree.srcPos) + res + } + + override def typedIf(tree: untpd.If, pt: Type)(using Context): Tree = + val condCtx = if tree.isInline then ctx.addMode(Mode.ForceInline) else ctx + typed(tree.cond, defn.BooleanType)(using condCtx) match { + case cond1 @ ConstantValue(b: Boolean) => + val selected0 = if (b) tree.thenp else tree.elsep + val selected = if (selected0.isEmpty) tpd.Literal(Constant(())) else typed(selected0, pt) + if (isIdempotentExpr(cond1)) selected + else Block(cond1 :: Nil, selected) + case cond1 => + if (tree.isInline) + errorTree(tree, + em"Cannot reduce `inline if` because its condition is not a constant value: $cond1") + else + cond1.computeNullableDeeply() + val if1 = untpd.cpy.If(tree)(cond = untpd.TypedSplice(cond1)) + super.typedIf(if1, pt) + } + + override def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree = + val vdef1 = + if sym.is(Inline) then + val rhs = typed(vdef.rhs) + sym.info = rhs.tpe + untpd.cpy.ValDef(vdef)(vdef.name, untpd.TypeTree(rhs.tpe), untpd.TypedSplice(rhs)) + else vdef + super.typedValDef(vdef1, sym) + + override def typedApply(tree: untpd.Apply, pt: Type)(using Context): Tree = + def cancelQuotes(tree: Tree): Tree = + tree match + case Quoted(Spliced(inner)) => inner + case _ => tree + val res = cancelQuotes(constToLiteral(betaReduce(super.typedApply(tree, pt)))) match { + case res: Apply if res.symbol == defn.QuotedRuntime_exprSplice + && StagingContext.level == 0 + && !hasInliningErrors => + val expanded = expandMacro(res.args.head, tree.srcPos) + typedExpr(expanded) // Inline calls and constant fold code generated by the macro + case res => + specializeEq(inlineIfNeeded(res)) + } + res + + override def typedTypeApply(tree: untpd.TypeApply, pt: Type)(using Context): Tree = + val tree1 = inlineIfNeeded(constToLiteral(betaReduce(super.typedTypeApply(tree, pt)))) + if tree1.symbol.isQuote then + ctx.compilationUnit.needsStaging = true + tree1 + + override def typedMatch(tree: untpd.Match, pt: Type)(using Context): Tree = + val tree1 = + if tree.isInline then + // TODO this might not be useful if we do not support #11291 + val sel1 = typedExpr(tree.selector)(using ctx.addMode(Mode.ForceInline)) + untpd.cpy.Match(tree)(sel1, tree.cases) + else tree + super.typedMatch(tree1, pt) + + override def typedMatchFinish(tree: untpd.Match, sel: Tree, wideSelType: Type, cases: List[untpd.CaseDef], pt: Type)(using Context) = + if (!tree.isInline || ctx.owner.isInlineMethod) // don't reduce match of nested inline method yet + super.typedMatchFinish(tree, sel, wideSelType, cases, pt) + else { + def selTyped(sel: Tree): Type = sel match { + case Typed(sel2, _) => selTyped(sel2) + case Block(Nil, sel2) => selTyped(sel2) + case Inlined(_, Nil, sel2) => selTyped(sel2) + case _ => sel.tpe + } + val selType = if (sel.isEmpty) wideSelType else selTyped(sel) + reduceInlineMatch(sel, selType, cases.asInstanceOf[List[CaseDef]], this) match { + case Some((caseBindings, rhs0)) => + // drop type ascriptions/casts hiding pattern-bound types (which are now aliases after reducing the match) + // note that any actually necessary casts will be reinserted by the typing pass below + val rhs1 = rhs0 match { + case Block(stats, t) if t.span.isSynthetic => + t match { + case Typed(expr, _) => + Block(stats, expr) + case TypeApply(sel@Select(expr, _), _) if sel.symbol.isTypeCast => + Block(stats, expr) + case _ => + rhs0 + } + case _ => rhs0 + } + val (usedBindings, rhs2) = dropUnusedDefs(caseBindings, rhs1) + val rhs = seq(usedBindings, rhs2) + inlining.println(i"""--- reduce: + |$tree + |--- to: + |$rhs""") + typedExpr(rhs, pt) + case None => + def guardStr(guard: untpd.Tree) = if (guard.isEmpty) "" else i" if $guard" + def patStr(cdef: untpd.CaseDef) = i"case ${cdef.pat}${guardStr(cdef.guard)}" + val msg = + if (tree.selector.isEmpty) + em"""cannot reduce summonFrom with + | patterns : ${tree.cases.map(patStr).mkString("\n ")}""" + else + em"""cannot reduce inline match with + | scrutinee: $sel : ${selType} + | patterns : ${tree.cases.map(patStr).mkString("\n ")}""" + errorTree(tree, msg) + } + } + + override def newLikeThis(nestingLevel: Int): Typer = new InlineTyper(initialErrorCount, nestingLevel) + + /** True if this inline typer has already issued errors */ + override def hasInliningErrors(using Context) = ctx.reporter.errorCount > initialErrorCount + + private def inlineIfNeeded(tree: Tree)(using Context): Tree = + val meth = tree.symbol + if meth.isAllOf(DeferredInline) then + errorTree(tree, i"Deferred inline ${meth.showLocated} cannot be invoked") + else if Inlines.needsInlining(tree) then Inlines.inlineCall(tree) + else tree + + override def typedUnadapted(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree = + super.typedUnadapted(tree, pt, locked) match + case member: MemberDef => member.setDefTree + case tree => tree + + private def specializeEq(tree: Tree): Tree = + tree match + case Apply(sel @ Select(arg1, opName), arg2 :: Nil) + if sel.symbol == defn.Any_== || sel.symbol == defn.Any_!= => + defn.ScalaValueClasses().find { cls => + arg1.tpe.derivesFrom(cls) && arg2.tpe.derivesFrom(cls) + } match + case Some(cls) => + val newOp = cls.requiredMethod(opName, List(cls.typeRef)) + arg1.select(newOp).withSpan(sel.span).appliedTo(arg2).withSpan(tree.span) + case None => tree + case _ => + tree + end InlineTyper + + /** Drop any side-effect-free bindings that are unused in expansion or other reachable bindings. + * Inline def bindings that are used only once. + */ + private def dropUnusedDefs(bindings: List[MemberDef], tree: Tree)(using Context): (List[MemberDef], Tree) = { + // inlining.println(i"drop unused $bindings%, % in $tree") + val (termBindings, typeBindings) = bindings.partition(_.symbol.isTerm) + if (typeBindings.nonEmpty) { + val typeBindingsSet = typeBindings.foldLeft[SimpleIdentitySet[Symbol]](SimpleIdentitySet.empty)(_ + _.symbol) + val inlineTypeBindings = new TreeTypeMap( + typeMap = new TypeMap() { + override def apply(tp: Type): Type = tp match { + case tr: TypeRef if tr.prefix.eq(NoPrefix) && typeBindingsSet.contains(tr.symbol) => + val TypeAlias(res) = tr.info: @unchecked + res + case tp => mapOver(tp) + } + }, + treeMap = { + case ident: Ident if ident.isType && typeBindingsSet.contains(ident.symbol) => + val TypeAlias(r) = ident.symbol.info: @unchecked + TypeTree(r).withSpan(ident.span) + case tree => tree + } + ) + val Block(termBindings1, tree1) = inlineTypeBindings(Block(termBindings, tree)) + dropUnusedDefs(termBindings1.asInstanceOf[List[ValOrDefDef]], tree1) + } + else { + val refCount = MutableSymbolMap[Int]() + val bindingOfSym = MutableSymbolMap[MemberDef]() + + def isInlineable(binding: MemberDef) = binding match { + case ddef @ DefDef(_, Nil, _, _) => isElideableExpr(ddef.rhs) + case vdef @ ValDef(_, _, _) => isElideableExpr(vdef.rhs) + case _ => false + } + for (binding <- bindings if isInlineable(binding)) { + refCount(binding.symbol) = 0 + bindingOfSym(binding.symbol) = binding + } + + val countRefs = new TreeTraverser { + override def traverse(t: Tree)(using Context) = { + def updateRefCount(sym: Symbol, inc: Int) = + for (x <- refCount.get(sym)) refCount(sym) = x + inc + def updateTermRefCounts(t: Tree) = + t.typeOpt.foreachPart { + case ref: TermRef => updateRefCount(ref.symbol, 2) // can't be inlined, so make sure refCount is at least 2 + case _ => + } + + t match { + case t: RefTree => + updateRefCount(t.symbol, 1) + updateTermRefCounts(t) + case _: New | _: TypeTree => + updateTermRefCounts(t) + case _ => + } + traverseChildren(t) + } + } + countRefs.traverse(tree) + for (binding <- bindings) countRefs.traverse(binding) + + def retain(boundSym: Symbol) = { + refCount.get(boundSym) match { + case Some(x) => x > 1 || x == 1 && !boundSym.is(Method) + case none => true + } + } && !boundSym.is(Inline) + + val inlineBindings = new TreeMap { + override def transform(t: Tree)(using Context) = t match { + case t: RefTree => + val sym = t.symbol + val t1 = refCount.get(sym) match { + case Some(1) => + bindingOfSym(sym) match { + case binding: ValOrDefDef => integrate(binding.rhs, sym) + } + case none => t + } + super.transform(t1) + case t: Apply => + val t1 = super.transform(t) + if (t1 `eq` t) t else reducer.betaReduce(t1) + case Block(Nil, expr) => + super.transform(expr) + case _ => + super.transform(t) + } + } + + val retained = bindings.filterConserve(binding => retain(binding.symbol)) + if (retained `eq` bindings) + (bindings, tree) + else { + val expanded = inlineBindings.transform(tree) + dropUnusedDefs(retained, expanded) + } + } + } + + private def expandMacro(body: Tree, splicePos: SrcPos)(using Context) = { + assert(StagingContext.level == 0) + val inlinedFrom = enclosingInlineds.last + val dependencies = macroDependencies(body) + val suspendable = ctx.compilationUnit.isSuspendable + if dependencies.nonEmpty && !ctx.reporter.errorsReported then + for sym <- dependencies do + if ctx.compilationUnit.source.file == sym.associatedFile then + report.error(em"Cannot call macro $sym defined in the same source file", call.srcPos) + if (suspendable && ctx.settings.XprintSuspension.value) + report.echo(i"suspension triggered by macro call to ${sym.showLocated} in ${sym.associatedFile}", call.srcPos) + if suspendable then + ctx.compilationUnit.suspend() // this throws a SuspendException + + val evaluatedSplice = inContext(quoted.MacroExpansion.context(inlinedFrom)) { + Splicer.splice(body, splicePos, inlinedFrom.srcPos, MacroClassLoader.fromContext) + } + val inlinedNormailizer = new TreeMap { + override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match { + case Inlined(EmptyTree, Nil, expr) if enclosingInlineds.isEmpty => transform(expr) + case _ => super.transform(tree) + } + } + val normalizedSplice = inlinedNormailizer.transform(evaluatedSplice) + if (normalizedSplice.isEmpty) normalizedSplice + else normalizedSplice.withSpan(splicePos.span) + } + + /** Return the set of symbols that are referred at level -1 by the tree and defined in the current run. + * This corresponds to the symbols that will need to be interpreted. + */ + private def macroDependencies(tree: Tree)(using Context) = + new TreeAccumulator[List[Symbol]] { + private var level = -1 + override def apply(syms: List[Symbol], tree: tpd.Tree)(using Context): List[Symbol] = + if (level != -1) foldOver(syms, tree) + else tree match { + case tree: RefTree if level == -1 && tree.symbol.isDefinedInCurrentRun && !tree.symbol.isLocal => + foldOver(tree.symbol :: syms, tree) + case Quoted(body) => + level += 1 + try apply(syms, body) + finally level -= 1 + case Spliced(body) => + level -= 1 + try apply(syms, body) + finally level += 1 + case SplicedType(body) => + level -= 1 + try apply(syms, body) + finally level += 1 + case _ => + foldOver(syms, tree) + } + }.apply(Nil, tree) +end Inliner diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala new file mode 100644 index 000000000000..f62de57722d7 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -0,0 +1,458 @@ +package dotty.tools +package dotc +package inlines + +import ast.*, core.* +import Flags.*, Symbols.*, Types.*, Decorators.*, Constants.*, Contexts.* +import StdNames.tpnme +import transform.SymUtils._ +import typer.* +import NameKinds.BodyRetainerName +import SymDenotations.SymDenotation +import config.Printers.inlining +import ErrorReporting.errorTree +import dotty.tools.dotc.util.{SourceFile, SourcePosition, SrcPos} +import parsing.Parsers.Parser +import transform.{PostTyper, Inlining, CrossVersionChecks} + +import collection.mutable +import reporting.trace +import util.Spans.Span + +/** Support for querying inlineable methods and for inlining calls to such methods */ +object Inlines: + import tpd._ + + /** An exception signalling that an inline info cannot be computed due to a + * cyclic reference. i14772.scala shows a case where this happens. + */ + private[dotc] class MissingInlineInfo extends Exception + + /** `sym` is an inline method with a known body to inline. + */ + def hasBodyToInline(sym: SymDenotation)(using Context): Boolean = + sym.isInlineMethod && sym.hasAnnotation(defn.BodyAnnot) + + /** The body to inline for method `sym`, or `EmptyTree` if none exists. + * @pre hasBodyToInline(sym) + */ + def bodyToInline(sym: SymDenotation)(using Context): Tree = + if hasBodyToInline(sym) then + sym.getAnnotation(defn.BodyAnnot).get.tree + else + EmptyTree + + /** Are we in an inline method body? */ + def inInlineMethod(using Context): Boolean = + ctx.owner.ownersIterator.exists(_.isInlineMethod) + + /** Can a call to method `meth` be inlined? */ + def isInlineable(meth: Symbol)(using Context): Boolean = + meth.is(Inline) && meth.hasAnnotation(defn.BodyAnnot) && !inInlineMethod + + /** Should call be inlined in this context? */ + def needsInlining(tree: Tree)(using Context): Boolean = tree match { + case Block(_, expr) => needsInlining(expr) + case _ => + isInlineable(tree.symbol) + && !tree.tpe.widenTermRefExpr.isInstanceOf[MethodOrPoly] + && StagingContext.level == 0 + && ( + ctx.phase == Phases.inliningPhase + || (ctx.phase == Phases.typerPhase && needsTransparentInlining(tree)) + ) + && !ctx.typer.hasInliningErrors + && !ctx.base.stopInlining + } + + private def needsTransparentInlining(tree: Tree)(using Context): Boolean = + tree.symbol.is(Transparent) + || ctx.mode.is(Mode.ForceInline) + || ctx.settings.YforceInlineWhileTyping.value + + /** Try to inline a call to an inline method. Fail with error if the maximal + * inline depth is exceeded. + * + * @param tree The call to inline + * @param pt The expected type of the call. + * @return An `Inlined` node that refers to the original call and the inlined bindings + * and body that replace it. + */ + def inlineCall(tree: Tree)(using Context): Tree = + if tree.symbol.denot != SymDenotations.NoDenotation + && tree.symbol.effectiveOwner == defn.CompiletimeTestingPackage.moduleClass + then + if (tree.symbol == defn.CompiletimeTesting_typeChecks) return Intrinsics.typeChecks(tree) + if (tree.symbol == defn.CompiletimeTesting_typeCheckErrors) return Intrinsics.typeCheckErrors(tree) + + CrossVersionChecks.checkExperimentalRef(tree.symbol, tree.srcPos) + + if tree.symbol.isConstructor then return tree // error already reported for the inline constructor definition + + /** Set the position of all trees logically contained in the expansion of + * inlined call `call` to the position of `call`. This transform is necessary + * when lifting bindings from the expansion to the outside of the call. + */ + def liftFromInlined(call: Tree) = new TreeMap: + override def transform(t: Tree)(using Context) = + if call.span.exists then + t match + case Inlined(t, Nil, expr) if t.isEmpty => expr + case _ if t.isEmpty => t + case _ => super.transform(t.withSpan(call.span)) + else t + end liftFromInlined + + val bindings = new mutable.ListBuffer[Tree] + + /** Lift bindings around inline call or in its function part to + * the `bindings` buffer. This is done as an optimization to keep + * inline call expansions smaller. + */ + def liftBindings(tree: Tree, liftPos: Tree => Tree): Tree = tree match { + case Block(stats, expr) => + bindings ++= stats.map(liftPos) + liftBindings(expr, liftPos) + case Inlined(call, stats, expr) => + bindings ++= stats.map(liftPos) + val lifter = liftFromInlined(call) + cpy.Inlined(tree)(call, Nil, liftBindings(expr, liftFromInlined(call).transform(_))) + case Apply(fn, args) => + cpy.Apply(tree)(liftBindings(fn, liftPos), args) + case TypeApply(fn, args) => + fn.tpe.widenTermRefExpr match + case tp: PolyType => + val targBounds = tp.instantiateParamInfos(args.map(_.tpe)) + for case (arg, bounds: TypeBounds) <- args.zip(targBounds) if !bounds.contains(arg.tpe) do + val boundsStr = + if bounds == TypeBounds.empty then " <: Any. Note that this type is higher-kinded." + else bounds.show + report.error(em"${arg.tpe} does not conform to bound$boundsStr", arg) + cpy.TypeApply(tree)(liftBindings(fn, liftPos), args) + case Select(qual, name) => + cpy.Select(tree)(liftBindings(qual, liftPos), name) + case _ => + tree + } + + // assertAllPositioned(tree) // debug + val tree1 = liftBindings(tree, identity) + val tree2 = + if bindings.nonEmpty then + cpy.Block(tree)(bindings.toList, inlineCall(tree1)) + else if enclosingInlineds.length < ctx.settings.XmaxInlines.value && !reachedInlinedTreesLimit then + val body = + try bodyToInline(tree.symbol) // can typecheck the tree and thereby produce errors + catch case _: MissingInlineInfo => + throw CyclicReference(ctx.owner) + new InlineCall(tree).expand(body) + else + ctx.base.stopInlining = true + val (reason, setting) = + if reachedInlinedTreesLimit then ("inlined trees", ctx.settings.XmaxInlinedTrees) + else ("successive inlines", ctx.settings.XmaxInlines) + errorTree( + tree, + i"""|Maximal number of $reason (${setting.value}) exceeded, + |Maybe this is caused by a recursive inline method? + |You can use ${setting.name} to change the limit.""", + (tree :: enclosingInlineds).last.srcPos + ) + if ctx.base.stopInlining && enclosingInlineds.isEmpty then + ctx.base.stopInlining = false + // we have completely backed out of the call that overflowed; + // reset so that further inline calls can be expanded + tree2 + end inlineCall + + /** Try to inline a pattern with an inline unapply method. Fail with error if the maximal + * inline depth is exceeded. + * + * @param unapp The tree of the pattern to inline + * @return An `Unapply` with a `fun` containing the inlined call to the unapply + */ + def inlinedUnapply(unapp: tpd.UnApply)(using Context): Tree = + // We cannot inline the unapply directly, since the pattern matcher relies on unapply applications + // as signposts what to do. On the other hand, we can do the inlining only in typer, not afterwards. + // So the trick is to create a "wrapper" unapply in an anonymous class that has the inlined unapply + // as its right hand side. The call to the wrapper unapply serves as the signpost for pattern matching. + // After pattern matching, the anonymous class is removed in phase InlinePatterns with a beta reduction step. + // + // An inline unapply `P.unapply` in a plattern `P(x1,x2,...)` is transformed into + // `{ class $anon { def unapply(t0: T0)(using t1: T1, t2: T2, ...): R = P.unapply(t0)(using t1, t2, ...) }; new $anon }.unapply` + // and the call `P.unapply(x1, x2, ...)` is inlined. + // This serves as a placeholder for the inlined body until the `patternMatcher` phase. After pattern matcher + // transforms the patterns into terms, the `inlinePatterns` phase removes this anonymous class by β-reducing + // the call to the `unapply`. + + val UnApply(fun, implicits, patterns) = unapp + val sym = unapp.symbol + val cls = newNormalizedClassSymbol(ctx.owner, tpnme.ANON_CLASS, Synthetic | Final, List(defn.ObjectType), coord = sym.coord) + val constr = newConstructor(cls, Synthetic, Nil, Nil, coord = sym.coord).entered + + val targs = fun match + case TypeApply(_, targs) => targs + case _ => Nil + val unapplyInfo = sym.info match + case info: PolyType => info.instantiate(targs.map(_.tpe)) + case info => info + + val unappplySym = newSymbol(cls, sym.name.toTermName, Synthetic | Method, unapplyInfo, coord = sym.coord).entered + val unapply = DefDef(unappplySym, argss => + inlineCall(fun.appliedToArgss(argss).withSpan(unapp.span))(using ctx.withOwner(unappplySym)) + ) + val cdef = ClassDef(cls, DefDef(constr), List(unapply)) + val newUnapply = Block(cdef :: Nil, New(cls.typeRef, Nil)) + val newFun = newUnapply.select(unappplySym).withSpan(unapp.span) + cpy.UnApply(unapp)(newFun, implicits, patterns) + end inlinedUnapply + + /** For a retained inline method, another method that keeps track of + * the body that is kept at runtime. For instance, an inline method + * + * inline override def f(x: T) = b + * + * is complemented by the body retainer method + * + * private def f$retainedBody(x: T) = f(x) + * + * where the call `f(x)` is inline-expanded. This body is then transferred + * back to `f` at erasure, using method addRetainedInlineBodies. + */ + def bodyRetainer(mdef: DefDef)(using Context): DefDef = + val meth = mdef.symbol.asTerm + + val retainer = meth.copy( + name = BodyRetainerName(meth.name), + flags = meth.flags &~ (Inline | Macro | Override) | Private, + coord = mdef.rhs.span.startPos).asTerm + retainer.deriveTargetNameAnnotation(meth, name => BodyRetainerName(name.asTermName)) + DefDef(retainer, prefss => + inlineCall( + ref(meth).appliedToArgss(prefss).withSpan(mdef.rhs.span.startPos))( + using ctx.withOwner(retainer))) + .showing(i"retainer for $meth: $result", inlining) + + /** Replace `Inlined` node by a block that contains its bindings and expansion */ + def dropInlined(inlined: Inlined)(using Context): Tree = + val tree1 = + if inlined.bindings.isEmpty then inlined.expansion + else cpy.Block(inlined)(inlined.bindings, inlined.expansion) + // Reposition in the outer most inlined call + if (enclosingInlineds.nonEmpty) tree1 else reposition(tree1, inlined.span) + + def reposition(tree: Tree, callSpan: Span)(using Context): Tree = + // Reference test tests/run/i4947b + + val curSource = ctx.compilationUnit.source + + // Tree copier that changes the source of all trees to `curSource` + val cpyWithNewSource = new TypedTreeCopier { + override protected def sourceFile(tree: tpd.Tree): SourceFile = curSource + override protected val untpdCpy: untpd.UntypedTreeCopier = new untpd.UntypedTreeCopier { + override protected def sourceFile(tree: untpd.Tree): SourceFile = curSource + } + } + + /** Removes all Inlined trees, replacing them with blocks. + * Repositions all trees directly inside an inlined expansion of a non empty call to the position of the call. + * Any tree directly inside an empty call (inlined in the inlined code) retains their position. + * + * Until we implement JSR-45, we cannot represent in output positions in other source files. + * So, reposition inlined code from other files with the call position. + */ + class Reposition extends TreeMap(cpyWithNewSource) { + + override def transform(tree: Tree)(using Context): Tree = { + def fixSpan[T <: untpd.Tree](copied: T): T = + copied.withSpan(if tree.source == curSource then tree.span else callSpan) + def finalize(copied: untpd.Tree) = + fixSpan(copied).withAttachmentsFrom(tree).withTypeUnchecked(tree.tpe) + + inContext(ctx.withSource(curSource)) { + tree match + case tree: Ident => finalize(untpd.Ident(tree.name)(curSource)) + case tree: Literal => finalize(untpd.Literal(tree.const)(curSource)) + case tree: This => finalize(untpd.This(tree.qual)(curSource)) + case tree: JavaSeqLiteral => finalize(untpd.JavaSeqLiteral(transform(tree.elems), transform(tree.elemtpt))(curSource)) + case tree: SeqLiteral => finalize(untpd.SeqLiteral(transform(tree.elems), transform(tree.elemtpt))(curSource)) + case tree: Bind => finalize(untpd.Bind(tree.name, transform(tree.body))(curSource)) + case tree: TypeTree => finalize(tpd.TypeTree(tree.tpe)) + case tree: DefTree => super.transform(tree).setDefTree + case EmptyTree => tree + case _ => fixSpan(super.transform(tree)) + } + } + } + + (new Reposition).transform(tree) + end reposition + + /** Leave only a call trace consisting of + * - a reference to the top-level class from which the call was inlined, + * - the call's position + * in the call field of an Inlined node. + * The trace has enough info to completely reconstruct positions. + * Note: For macros it returns a Select and for other inline methods it returns an Ident (this distinction is only temporary to be able to run YCheckPositions) + */ + def inlineCallTrace(callSym: Symbol, pos: SourcePosition)(using Context): Tree = { + assert(ctx.source == pos.source) + val topLevelCls = callSym.topLevelClass + if (callSym.is(Macro)) ref(topLevelCls.owner).select(topLevelCls.name)(using ctx.withOwner(topLevelCls.owner)).withSpan(pos.span) + else Ident(topLevelCls.typeRef).withSpan(pos.span) + } + + private object Intrinsics: + import dotty.tools.dotc.reporting.Diagnostic.Error + private enum ErrorKind: + case Parser, Typer + + private def compileForErrors(tree: Tree)(using Context): List[(ErrorKind, Error)] = + assert(tree.symbol == defn.CompiletimeTesting_typeChecks || tree.symbol == defn.CompiletimeTesting_typeCheckErrors) + def stripTyped(t: Tree): Tree = t match { + case Typed(t2, _) => stripTyped(t2) + case Block(Nil, t2) => stripTyped(t2) + case Inlined(_, Nil, t2) => stripTyped(t2) + case _ => t + } + + val Apply(_, codeArg :: Nil) = tree: @unchecked + val codeArg1 = stripTyped(codeArg.underlying) + val underlyingCodeArg = + if Inlines.isInlineable(codeArg1.symbol) then stripTyped(Inlines.inlineCall(codeArg1)) + else codeArg1 + + ConstFold(underlyingCodeArg).tpe.widenTermRefExpr match { + case ConstantType(Constant(code: String)) => + val source2 = SourceFile.virtual("tasty-reflect", code) + inContext(ctx.fresh.setNewTyperState().setTyper(new Typer(ctx.nestingLevel + 1)).setSource(source2)) { + val tree2 = new Parser(source2).block() + if ctx.reporter.allErrors.nonEmpty then + ctx.reporter.allErrors.map((ErrorKind.Parser, _)) + else + val tree3 = ctx.typer.typed(tree2) + ctx.base.postTyperPhase match + case postTyper: PostTyper if ctx.reporter.allErrors.isEmpty => + val tree4 = atPhase(postTyper) { postTyper.newTransformer.transform(tree3) } + ctx.base.inliningPhase match + case inlining: Inlining if ctx.reporter.allErrors.isEmpty => + atPhase(inlining) { inlining.newTransformer.transform(tree4) } + case _ => + case _ => + ctx.reporter.allErrors.map((ErrorKind.Typer, _)) + } + case t => + report.error(em"argument to compileError must be a statically known String but was: $codeArg", codeArg1.srcPos) + Nil + } + + private def packError(kind: ErrorKind, error: Error)(using Context): Tree = + def lit(x: Any) = Literal(Constant(x)) + val constructor: Tree = ref(defn.CompiletimeTesting_Error_apply) + val parserErrorKind: Tree = ref(defn.CompiletimeTesting_ErrorKind_Parser) + val typerErrorKind: Tree = ref(defn.CompiletimeTesting_ErrorKind_Typer) + + constructor.appliedTo( + lit(error.message), + lit(error.pos.lineContent.reverse.dropWhile("\n ".contains).reverse), + lit(error.pos.column), + if kind == ErrorKind.Parser then parserErrorKind else typerErrorKind) + + private def packErrors(errors: List[(ErrorKind, Error)], pos: SrcPos)(using Context): Tree = + val individualErrors: List[Tree] = errors.map(packError) + val errorTpt = ref(defn.CompiletimeTesting_ErrorClass).withSpan(pos.span) + mkList(individualErrors, errorTpt) + + /** Expand call to scala.compiletime.testing.typeChecks */ + def typeChecks(tree: Tree)(using Context): Tree = + val errors = compileForErrors(tree) + Literal(Constant(errors.isEmpty)).withSpan(tree.span) + + /** Expand call to scala.compiletime.testing.typeCheckErrors */ + def typeCheckErrors(tree: Tree)(using Context): Tree = + val errors = compileForErrors(tree) + packErrors(errors, tree) + + /** Expand call to scala.compiletime.codeOf */ + def codeOf(arg: Tree, pos: SrcPos)(using Context): Tree = + val ctx1 = ctx.fresh.setSetting(ctx.settings.color, "never") + Literal(Constant(arg.show(using ctx1))).withSpan(pos.span) + end Intrinsics + + /** Produces an inlined version of `call` via its `inlined` method. + * + * @param call the original call to an inlineable method + * @param rhsToInline the body of the inlineable method that replaces the call. + */ + private class InlineCall(call: tpd.Tree)(using Context) extends Inliner(call): + import tpd._ + import Inlines.* + + /** The Inlined node representing the inlined call */ + def expand(rhsToInline: Tree): Tree = + + // Special handling of `requireConst` and `codeOf` + callValueArgss match + case (arg :: Nil) :: Nil => + if inlinedMethod == defn.Compiletime_requireConst then + arg match + case ConstantValue(_) | Inlined(_, Nil, Typed(ConstantValue(_), _)) => // ok + case _ => report.error(em"expected a constant value but found: $arg", arg.srcPos) + return Literal(Constant(())).withSpan(call.span) + else if inlinedMethod == defn.Compiletime_codeOf then + return Intrinsics.codeOf(arg, call.srcPos) + case _ => + + // Special handling of `constValue[T]`, `constValueOpt[T], and summonInline[T]` + if callTypeArgs.length == 1 then + if (inlinedMethod == defn.Compiletime_constValue) { + val constVal = tryConstValue + if constVal.isEmpty then + val msg = em"not a constant type: ${callTypeArgs.head}; cannot take constValue" + return ref(defn.Predef_undefined).withSpan(call.span).withType(ErrorType(msg)) + else + return constVal + } + else if (inlinedMethod == defn.Compiletime_constValueOpt) { + val constVal = tryConstValue + return ( + if (constVal.isEmpty) ref(defn.NoneModule.termRef) + else New(defn.SomeClass.typeRef.appliedTo(constVal.tpe), constVal :: Nil) + ) + } + else if (inlinedMethod == defn.Compiletime_summonInline) { + def searchImplicit(tpt: Tree) = + val evTyper = new Typer(ctx.nestingLevel + 1) + val evCtx = ctx.fresh.setTyper(evTyper) + inContext(evCtx) { + val evidence = evTyper.inferImplicitArg(tpt.tpe, tpt.span) + evidence.tpe match + case fail: Implicits.SearchFailureType => + val msg = evTyper.missingArgMsg(evidence, tpt.tpe, "") + errorTree(call, em"$msg") + case _ => + evidence + } + return searchImplicit(callTypeArgs.head) + } + end if + + val (bindings, expansion) = super.inlined(rhsToInline) + + // Take care that only argument bindings go into `bindings`, since positions are + // different for bindings from arguments and bindings from body. + val res = tpd.Inlined(call, bindings, expansion) + + if !hasOpaqueProxies then res + else + val target = + if inlinedMethod.is(Transparent) then call.tpe & res.tpe + else call.tpe + res.ensureConforms(target) + // Make sure that the sealing with the declared type + // is type correct. Without it we might get problems since the + // expression's type is the opaque alias but the call's type is + // the opaque type itself. An example is in pos/opaque-inline1.scala. + end expand + end InlineCall +end Inlines \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala b/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala similarity index 98% rename from compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala rename to compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala index 5d45d1dce714..557a736d3d3c 100644 --- a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala +++ b/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala @@ -1,6 +1,6 @@ package dotty.tools package dotc -package typer +package inlines import dotty.tools.dotc.ast.{Trees, tpd, untpd} import Trees._ @@ -14,6 +14,7 @@ import StdNames.nme import Contexts._ import Names.{Name, TermName} import NameKinds.{InlineAccessorName, UniqueInlineName} +import inlines.Inlines import NameOps._ import Annotations._ import transform.{AccessProxies, PCPCheckAndHeal, Splicer} @@ -72,7 +73,7 @@ object PrepareInlineable { !sym.isContainedIn(inlineSym) && !(sym.isStableMember && sym.info.widenTermRefExpr.isInstanceOf[ConstantType]) && !sym.isInlineMethod && - (Inliner.inInlineMethod || StagingContext.level > 0) + (Inlines.inInlineMethod || StagingContext.level > 0) def preTransform(tree: Tree)(using Context): Tree @@ -282,7 +283,7 @@ object PrepareInlineable { } private def checkInlineMethod(inlined: Symbol, body: Tree)(using Context): body.type = { - if Inliner.inInlineMethod(using ctx.outer) then + if Inlines.inInlineMethod(using ctx.outer) then report.error(ex"Implementation restriction: nested inline methods are not supported", inlined.srcPos) if (inlined.is(Macro) && !ctx.isAfterTyper) { diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index c22172b3e10f..e561b26abf6d 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -16,7 +16,7 @@ import Types._ import Symbols._ import Names._ import NameOps._ -import typer.Inliner +import inlines.Inlines import transform.ValueClasses import transform.SymUtils._ import dotty.tools.io.File @@ -657,7 +657,7 @@ private class ExtractAPICollector(using Context) extends ThunkHolder { */ def apiAnnotations(s: Symbol, inlineOrigin: Symbol): List[api.Annotation] = { val annots = new mutable.ListBuffer[api.Annotation] - val inlineBody = Inliner.bodyToInline(s) + val inlineBody = Inlines.bodyToInline(s) if !inlineBody.isEmpty then // If the body of an inline def changes, all the reverse dependencies of // this method need to be recompiled. sbt has no way of tracking method diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index dd511f996f46..50a57da67349 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -19,7 +19,7 @@ import core.Constants._ import core.Definitions._ import core.Annotations.BodyAnnotation import typer.NoChecking -import typer.Inliner +import inlines.Inlines import typer.ProtoTypes._ import typer.ErrorReporting.errorTree import typer.Checking.checkValue @@ -886,7 +886,7 @@ object Erasure { override def typedInlined(tree: untpd.Inlined, pt: Type)(using Context): Tree = super.typedInlined(tree, pt) match { - case tree: Inlined => Inliner.dropInlined(tree) + case tree: Inlined => Inlines.dropInlined(tree) } override def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree = diff --git a/compiler/src/dotty/tools/dotc/transform/InlineVals.scala b/compiler/src/dotty/tools/dotc/transform/InlineVals.scala index 25fb573ccf10..65212ec2c0cc 100644 --- a/compiler/src/dotty/tools/dotc/transform/InlineVals.scala +++ b/compiler/src/dotty/tools/dotc/transform/InlineVals.scala @@ -8,7 +8,7 @@ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.Flags._ import dotty.tools.dotc.core.Types._ import dotty.tools.dotc.transform.MegaPhase.MiniPhase -import dotty.tools.dotc.typer.Inliner +import dotty.tools.dotc.inlines.Inlines /** Check that `tree.rhs` can be right hand-side of an `inline` value definition. */ class InlineVals extends MiniPhase: @@ -31,7 +31,7 @@ class InlineVals extends MiniPhase: /** Check that `tree.rhs` can be right hand-side of an `inline` value definition. */ private def checkInlineConformant(tree: ValDef)(using Context): Unit = { if tree.symbol.is(Inline, butNot = DeferredOrTermParamOrAccessor) - && !Inliner.inInlineMethod + && !Inlines.inInlineMethod then val rhs = tree.rhs val tpt = tree.tpt diff --git a/compiler/src/dotty/tools/dotc/transform/Inlining.scala b/compiler/src/dotty/tools/dotc/transform/Inlining.scala index 3c5da3ffadf3..5ddcf600c63a 100644 --- a/compiler/src/dotty/tools/dotc/transform/Inlining.scala +++ b/compiler/src/dotty/tools/dotc/transform/Inlining.scala @@ -9,7 +9,7 @@ import SymUtils._ import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.core.StagingContext._ -import dotty.tools.dotc.typer.Inliner +import dotty.tools.dotc.inlines.Inlines import dotty.tools.dotc.ast.TreeMapWithImplicits @@ -43,7 +43,7 @@ class Inlining extends MacroTransform { traverseChildren(tree)(using StagingContext.quoteContext) case _: GenericApply if tree.symbol.isExprSplice => traverseChildren(tree)(using StagingContext.spliceContext) - case tree: RefTree if !Inliner.inInlineMethod && StagingContext.level == 0 => + case tree: RefTree if !Inlines.inInlineMethod && StagingContext.level == 0 => assert(!tree.symbol.isInlineMethod, tree.show) case _ => traverseChildren(tree) @@ -64,10 +64,10 @@ class Inlining extends MacroTransform { else super.transform(tree) case _: Typed | _: Block => super.transform(tree) - case _ if Inliner.needsInlining(tree) => + case _ if Inlines.needsInlining(tree) => val tree1 = super.transform(tree) if tree1.tpe.isError then tree1 - else Inliner.inlineCall(tree1) + else Inlines.inlineCall(tree1) case _: GenericApply if tree.symbol.isQuote => super.transform(tree)(using StagingContext.quoteContext) case _: GenericApply if tree.symbol.isExprSplice => diff --git a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index 1cd5edf85052..f3ae6a377aab 100644 --- a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala @@ -20,7 +20,7 @@ import dotty.tools.dotc.core.Annotations._ import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.quoted._ import dotty.tools.dotc.transform.TreeMapWithStages._ -import dotty.tools.dotc.typer.Inliner +import dotty.tools.dotc.inlines.Inlines import scala.annotation.constructorOnly @@ -84,10 +84,10 @@ class PickleQuotes extends MacroTransform { override def checkPostCondition(tree: Tree)(using Context): Unit = tree match - case tree: RefTree if !Inliner.inInlineMethod => + case tree: RefTree if !Inlines.inInlineMethod => assert(!tree.symbol.isQuote) assert(!tree.symbol.isExprSplice) - case _ : TypeDef if !Inliner.inInlineMethod => + case _ : TypeDef if !Inlines.inInlineMethod => assert(!tree.symbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot), s"${tree.symbol} should have been removed by PickledQuotes because it has a @quoteTypeTag") case _ => @@ -101,7 +101,7 @@ class PickleQuotes extends MacroTransform { case Apply(Select(Apply(TypeApply(fn, List(tpt)), List(code)),nme.apply), List(quotes)) if fn.symbol == defn.QuotedRuntime_exprQuote => val (contents, codeWithHoles) = makeHoles(code) - val sourceRef = Inliner.inlineCallTrace(ctx.owner, tree.sourcePos) + val sourceRef = Inlines.inlineCallTrace(ctx.owner, tree.sourcePos) val codeWithHoles2 = Inlined(sourceRef, Nil, codeWithHoles) val pickled = PickleQuotes(quotes, codeWithHoles2, contents, tpt.tpe, false) transform(pickled) // pickle quotes that are in the contents diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 25c4c8fc91c3..dda4b89f6d1c 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -5,7 +5,7 @@ import dotty.tools.dotc.ast.{Trees, tpd, untpd, desugar} import scala.collection.mutable import core._ import dotty.tools.dotc.typer.Checking -import dotty.tools.dotc.typer.Inliner +import dotty.tools.dotc.inlines.Inlines import dotty.tools.dotc.typer.VarianceChecker import typer.ErrorReporting.errorTree import Types._, Contexts._, Names._, Flags._, DenotTransformers._, Phases._ @@ -282,7 +282,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase if tree.isType then checkNotPackage(tree) else - if tree.symbol.is(Inline) && !Inliner.inInlineMethod then + if tree.symbol.is(Inline) && !Inlines.inInlineMethod then ctx.compilationUnit.needsInlining = true checkNoConstructorProxy(tree) tree.tpe match { @@ -356,7 +356,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase } case Inlined(call, bindings, expansion) if !call.isEmpty => val pos = call.sourcePos - val callTrace = Inliner.inlineCallTrace(call.symbol, pos)(using ctx.withSource(pos.source)) + val callTrace = Inlines.inlineCallTrace(call.symbol, pos)(using ctx.withSource(pos.source)) cpy.Inlined(tree)(callTrace, transformSub(bindings), transform(expansion)(using inlineContext(call))) case templ: Template => withNoCheckNews(templ.parents.flatMap(newPart)) { diff --git a/compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala b/compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala index 0d25053a64cb..e462f82b1dad 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala @@ -18,7 +18,6 @@ import dotty.tools.dotc.core.Names._ import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.quoted._ import dotty.tools.dotc.transform.TreeMapWithStages._ -import dotty.tools.dotc.typer.Inliner import scala.annotation.constructorOnly diff --git a/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala b/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala index 0ee1ffad9239..5237ecbcef8a 100644 --- a/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala +++ b/compiler/src/dotty/tools/dotc/transform/SpecializeTuples.scala @@ -6,7 +6,7 @@ import ast.Trees.*, ast.tpd, core.* import Contexts.*, Types.*, Decorators.*, Symbols.*, DenotTransformers.* import SymDenotations.*, Scopes.*, StdNames.*, NameOps.*, Names.* import MegaPhase.MiniPhase -import typer.Inliner.isElideableExpr +import inlines.Inliner.isElideableExpr /** Specializes Tuples by replacing tuple construction and selection trees. * diff --git a/compiler/src/dotty/tools/dotc/transform/Splicing.scala b/compiler/src/dotty/tools/dotc/transform/Splicing.scala index 6035f18474f2..ad3f0322130d 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicing.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicing.scala @@ -22,7 +22,6 @@ import dotty.tools.dotc.core.Names._ import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.quoted._ import dotty.tools.dotc.transform.TreeMapWithStages._ -import dotty.tools.dotc.typer.Inliner import dotty.tools.dotc.config.ScalaRelease.* import scala.annotation.constructorOnly diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 0d93e83432fe..c636510fdb89 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -15,7 +15,6 @@ import ProtoTypes._ import Scopes._ import CheckRealizable._ import ErrorReporting.errorTree -import rewrites.Rewrites.patch import util.Spans.Span import Phases.refchecksPhase import Constants.Constant @@ -23,6 +22,7 @@ import Constants.Constant import util.SrcPos import util.Spans.Span import rewrites.Rewrites.patch +import inlines.Inlines import transform.SymUtils._ import transform.ValueClasses._ import Decorators._ @@ -1234,7 +1234,7 @@ trait Checking { /** Check that we are in an inline context (inside an inline method or in inline code) */ def checkInInlineContext(what: String, pos: SrcPos)(using Context): Unit = - if !Inliner.inInlineMethod && !ctx.isInlineContext then + if !Inlines.inInlineMethod && !ctx.isInlineContext then report.error(em"$what can only be used in an inline method", pos) /** Check arguments of compiler-defined annotations */ diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala deleted file mode 100644 index ad71c86318a1..000000000000 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ /dev/null @@ -1,1917 +0,0 @@ -package dotty.tools -package dotc -package typer - -import ast.{TreeInfo, tpd, _} -import core._ -import Flags._ -import Symbols._ -import Types._ -import Decorators._ -import Constants._ -import StagingContext._ -import StdNames._ -import transform.SymUtils._ -import Contexts._ -import Names.{Name, TermName} -import NameKinds.{InlineAccessorName, InlineBinderName, InlineScrutineeName, BodyRetainerName} -import ProtoTypes.shallowSelectionProto -import SymDenotations.SymDenotation -import Inferencing.isFullyDefined -import Scopes.newScope -import config.Printers.inlining -import config.Feature -import ErrorReporting.errorTree -import dotty.tools.dotc.util.{SimpleIdentityMap, SimpleIdentitySet, SourceFile, SourcePosition, SrcPos} -import dotty.tools.dotc.parsing.Parsers.Parser -import Nullables._ -import transform.{PostTyper, Inlining, CrossVersionChecks} - -import collection.mutable -import reporting.trace -import util.Spans.Span -import dotty.tools.dotc.transform.Splicer -import quoted.QuoteUtils - -import scala.annotation.constructorOnly - -object Inliner { - import tpd._ - - private type DefBuffer = mutable.ListBuffer[ValOrDefDef] - - /** An exception signalling that an inline info cannot be computed due to a - * cyclic reference. i14772.scala shows a case where this happens. - */ - private[typer] class MissingInlineInfo extends Exception - - /** `sym` is an inline method with a known body to inline. - */ - def hasBodyToInline(sym: SymDenotation)(using Context): Boolean = - sym.isInlineMethod && sym.hasAnnotation(defn.BodyAnnot) - - /** The body to inline for method `sym`, or `EmptyTree` if none exists. - * @pre hasBodyToInline(sym) - */ - def bodyToInline(sym: SymDenotation)(using Context): Tree = - if hasBodyToInline(sym) then - sym.getAnnotation(defn.BodyAnnot).get.tree - else - EmptyTree - - /** Are we in an inline method body? */ - def inInlineMethod(using Context): Boolean = - ctx.owner.ownersIterator.exists(_.isInlineMethod) - - /** Can a call to method `meth` be inlined? */ - def isInlineable(meth: Symbol)(using Context): Boolean = - meth.is(Inline) && meth.hasAnnotation(defn.BodyAnnot) && !inInlineMethod - - /** Should call be inlined in this context? */ - def needsInlining(tree: Tree)(using Context): Boolean = tree match { - case Block(_, expr) => needsInlining(expr) - case _ => - isInlineable(tree.symbol) - && !tree.tpe.widenTermRefExpr.isInstanceOf[MethodOrPoly] - && StagingContext.level == 0 - && ( - ctx.phase == Phases.inliningPhase - || (ctx.phase == Phases.typerPhase && needsTransparentInlining(tree)) - ) - && !ctx.typer.hasInliningErrors - && !ctx.base.stopInlining - } - - private def needsTransparentInlining(tree: Tree)(using Context): Boolean = - tree.symbol.is(Transparent) - || ctx.mode.is(Mode.ForceInline) - || ctx.settings.YforceInlineWhileTyping.value - - /** Try to inline a call to an inline method. Fail with error if the maximal - * inline depth is exceeded. - * - * @param tree The call to inline - * @param pt The expected type of the call. - * @return An `Inlined` node that refers to the original call and the inlined bindings - * and body that replace it. - */ - def inlineCall(tree: Tree)(using Context): Tree = { - if tree.symbol.denot != SymDenotations.NoDenotation - && tree.symbol.effectiveOwner == defn.CompiletimeTestingPackage.moduleClass - then - if (tree.symbol == defn.CompiletimeTesting_typeChecks) return Intrinsics.typeChecks(tree) - if (tree.symbol == defn.CompiletimeTesting_typeCheckErrors) return Intrinsics.typeCheckErrors(tree) - - CrossVersionChecks.checkExperimentalRef(tree.symbol, tree.srcPos) - - if tree.symbol.isConstructor then return tree // error already reported for the inline constructor definition - - /** Set the position of all trees logically contained in the expansion of - * inlined call `call` to the position of `call`. This transform is necessary - * when lifting bindings from the expansion to the outside of the call. - */ - def liftFromInlined(call: Tree) = new TreeMap: - override def transform(t: Tree)(using Context) = - if call.span.exists then - t match - case Inlined(t, Nil, expr) if t.isEmpty => expr - case _ if t.isEmpty => t - case _ => super.transform(t.withSpan(call.span)) - else t - end liftFromInlined - - val bindings = new mutable.ListBuffer[Tree] - - /** Lift bindings around inline call or in its function part to - * the `bindings` buffer. This is done as an optimization to keep - * inline call expansions smaller. - */ - def liftBindings(tree: Tree, liftPos: Tree => Tree): Tree = tree match { - case Block(stats, expr) => - bindings ++= stats.map(liftPos) - liftBindings(expr, liftPos) - case Inlined(call, stats, expr) => - bindings ++= stats.map(liftPos) - val lifter = liftFromInlined(call) - cpy.Inlined(tree)(call, Nil, liftBindings(expr, liftFromInlined(call).transform(_))) - case Apply(fn, args) => - cpy.Apply(tree)(liftBindings(fn, liftPos), args) - case TypeApply(fn, args) => - fn.tpe.widenTermRefExpr match - case tp: PolyType => - val targBounds = tp.instantiateParamInfos(args.map(_.tpe)) - for case (arg, bounds: TypeBounds) <- args.zip(targBounds) if !bounds.contains(arg.tpe) do - val boundsStr = - if bounds == TypeBounds.empty then " <: Any. Note that this type is higher-kinded." - else bounds.show - report.error(em"${arg.tpe} does not conform to bound$boundsStr", arg) - cpy.TypeApply(tree)(liftBindings(fn, liftPos), args) - case Select(qual, name) => - cpy.Select(tree)(liftBindings(qual, liftPos), name) - case _ => - tree - } - - // assertAllPositioned(tree) // debug - val tree1 = liftBindings(tree, identity) - val tree2 = - if bindings.nonEmpty then - cpy.Block(tree)(bindings.toList, inlineCall(tree1)) - else if enclosingInlineds.length < ctx.settings.XmaxInlines.value && !reachedInlinedTreesLimit then - val body = - try bodyToInline(tree.symbol) // can typecheck the tree and thereby produce errors - catch case _: MissingInlineInfo => - throw CyclicReference(ctx.owner) - new Inliner(tree, body).inlined(tree.srcPos) - else - ctx.base.stopInlining = true - val (reason, setting) = - if reachedInlinedTreesLimit then ("inlined trees", ctx.settings.XmaxInlinedTrees) - else ("successive inlines", ctx.settings.XmaxInlines) - errorTree( - tree, - i"""|Maximal number of $reason (${setting.value}) exceeded, - |Maybe this is caused by a recursive inline method? - |You can use ${setting.name} to change the limit.""", - (tree :: enclosingInlineds).last.srcPos - ) - if ctx.base.stopInlining && enclosingInlineds.isEmpty then - ctx.base.stopInlining = false - // we have completely backed out of the call that overflowed; - // reset so that further inline calls can be expanded - tree2 - } - - /** Try to inline a pattern with an inline unapply method. Fail with error if the maximal - * inline depth is exceeded. - * - * @param unapp The tree of the pattern to inline - * @return An `Unapply` with a `fun` containing the inlined call to the unapply - */ - def inlinedUnapply(unapp: tpd.UnApply)(using Context): Tree = { - // We cannot inline the unapply directly, since the pattern matcher relies on unapply applications - // as signposts what to do. On the other hand, we can do the inlining only in typer, not afterwards. - // So the trick is to create a "wrapper" unapply in an anonymous class that has the inlined unapply - // as its right hand side. The call to the wrapper unapply serves as the signpost for pattern matching. - // After pattern matching, the anonymous class is removed in phase InlinePatterns with a beta reduction step. - // - // An inline unapply `P.unapply` in a plattern `P(x1,x2,...)` is transformed into - // `{ class $anon { def unapply(t0: T0)(using t1: T1, t2: T2, ...): R = P.unapply(t0)(using t1, t2, ...) }; new $anon }.unapply` - // and the call `P.unapply(x1, x2, ...)` is inlined. - // This serves as a placeholder for the inlined body until the `patternMatcher` phase. After pattern matcher - // transforms the patterns into terms, the `inlinePatterns` phase removes this anonymous class by β-reducing - // the call to the `unapply`. - - val UnApply(fun, implicits, patterns) = unapp - val sym = unapp.symbol - val cls = newNormalizedClassSymbol(ctx.owner, tpnme.ANON_CLASS, Synthetic | Final, List(defn.ObjectType), coord = sym.coord) - val constr = newConstructor(cls, Synthetic, Nil, Nil, coord = sym.coord).entered - - val targs = fun match - case TypeApply(_, targs) => targs - case _ => Nil - val unapplyInfo = sym.info match - case info: PolyType => info.instantiate(targs.map(_.tpe)) - case info => info - - val unappplySym = newSymbol(cls, sym.name.toTermName, Synthetic | Method, unapplyInfo, coord = sym.coord).entered - val unapply = DefDef(unappplySym, argss => - inlineCall(fun.appliedToArgss(argss).withSpan(unapp.span))(using ctx.withOwner(unappplySym)) - ) - val cdef = ClassDef(cls, DefDef(constr), List(unapply)) - val newUnapply = Block(cdef :: Nil, New(cls.typeRef, Nil)) - val newFun = newUnapply.select(unappplySym).withSpan(unapp.span) - cpy.UnApply(unapp)(newFun, implicits, patterns) - } - - /** For a retained inline method, another method that keeps track of - * the body that is kept at runtime. For instance, an inline method - * - * inline override def f(x: T) = b - * - * is complemented by the body retainer method - * - * private def f$retainedBody(x: T) = f(x) - * - * where the call `f(x)` is inline-expanded. This body is then transferred - * back to `f` at erasure, using method addRetainedInlineBodies. - */ - def bodyRetainer(mdef: DefDef)(using Context): DefDef = - val meth = mdef.symbol.asTerm - - val retainer = meth.copy( - name = BodyRetainerName(meth.name), - flags = meth.flags &~ (Inline | Macro | Override) | Private, - coord = mdef.rhs.span.startPos).asTerm - retainer.deriveTargetNameAnnotation(meth, name => BodyRetainerName(name.asTermName)) - DefDef(retainer, prefss => - inlineCall( - ref(meth).appliedToArgss(prefss).withSpan(mdef.rhs.span.startPos))( - using ctx.withOwner(retainer))) - .showing(i"retainer for $meth: $result", inlining) - - /** Replace `Inlined` node by a block that contains its bindings and expansion */ - def dropInlined(inlined: Inlined)(using Context): Tree = - val tree1 = - if inlined.bindings.isEmpty then inlined.expansion - else cpy.Block(inlined)(inlined.bindings, inlined.expansion) - // Reposition in the outer most inlined call - if (enclosingInlineds.nonEmpty) tree1 else reposition(tree1, inlined.span) - - def reposition(tree: Tree, callSpan: Span)(using Context): Tree = { - // Reference test tests/run/i4947b - - val curSource = ctx.compilationUnit.source - - // Tree copier that changes the source of all trees to `curSource` - val cpyWithNewSource = new TypedTreeCopier { - override protected def sourceFile(tree: tpd.Tree): SourceFile = curSource - override protected val untpdCpy: untpd.UntypedTreeCopier = new untpd.UntypedTreeCopier { - override protected def sourceFile(tree: untpd.Tree): SourceFile = curSource - } - } - - /** Removes all Inlined trees, replacing them with blocks. - * Repositions all trees directly inside an inlined expansion of a non empty call to the position of the call. - * Any tree directly inside an empty call (inlined in the inlined code) retains their position. - * - * Until we implement JSR-45, we cannot represent in output positions in other source files. - * So, reposition inlined code from other files with the call position. - */ - class Reposition extends TreeMap(cpyWithNewSource) { - - override def transform(tree: Tree)(using Context): Tree = { - def fixSpan[T <: untpd.Tree](copied: T): T = - copied.withSpan(if tree.source == curSource then tree.span else callSpan) - def finalize(copied: untpd.Tree) = - fixSpan(copied).withAttachmentsFrom(tree).withTypeUnchecked(tree.tpe) - - inContext(ctx.withSource(curSource)) { - tree match - case tree: Ident => finalize(untpd.Ident(tree.name)(curSource)) - case tree: Literal => finalize(untpd.Literal(tree.const)(curSource)) - case tree: This => finalize(untpd.This(tree.qual)(curSource)) - case tree: JavaSeqLiteral => finalize(untpd.JavaSeqLiteral(transform(tree.elems), transform(tree.elemtpt))(curSource)) - case tree: SeqLiteral => finalize(untpd.SeqLiteral(transform(tree.elems), transform(tree.elemtpt))(curSource)) - case tree: Bind => finalize(untpd.Bind(tree.name, transform(tree.body))(curSource)) - case tree: TypeTree => finalize(tpd.TypeTree(tree.tpe)) - case tree: DefTree => super.transform(tree).setDefTree - case EmptyTree => tree - case _ => fixSpan(super.transform(tree)) - } - } - } - - (new Reposition).transform(tree) - } - - /** Leave only a call trace consisting of - * - a reference to the top-level class from which the call was inlined, - * - the call's position - * in the call field of an Inlined node. - * The trace has enough info to completely reconstruct positions. - * Note: For macros it returns a Select and for other inline methods it returns an Ident (this distinction is only temporary to be able to run YCheckPositions) - */ - def inlineCallTrace(callSym: Symbol, pos: SourcePosition)(using Context): Tree = { - assert(ctx.source == pos.source) - val topLevelCls = callSym.topLevelClass - if (callSym.is(Macro)) ref(topLevelCls.owner).select(topLevelCls.name)(using ctx.withOwner(topLevelCls.owner)).withSpan(pos.span) - else Ident(topLevelCls.typeRef).withSpan(pos.span) - } - - object Intrinsics { - import dotty.tools.dotc.reporting.Diagnostic.Error - private enum ErrorKind: - case Parser, Typer - - private def compileForErrors(tree: Tree)(using Context): List[(ErrorKind, Error)] = - assert(tree.symbol == defn.CompiletimeTesting_typeChecks || tree.symbol == defn.CompiletimeTesting_typeCheckErrors) - def stripTyped(t: Tree): Tree = t match { - case Typed(t2, _) => stripTyped(t2) - case Block(Nil, t2) => stripTyped(t2) - case Inlined(_, Nil, t2) => stripTyped(t2) - case _ => t - } - - val Apply(_, codeArg :: Nil) = tree: @unchecked - val codeArg1 = stripTyped(codeArg.underlying) - val underlyingCodeArg = - if Inliner.isInlineable(codeArg1.symbol) then stripTyped(Inliner.inlineCall(codeArg1)) - else codeArg1 - - ConstFold(underlyingCodeArg).tpe.widenTermRefExpr match { - case ConstantType(Constant(code: String)) => - val source2 = SourceFile.virtual("tasty-reflect", code) - inContext(ctx.fresh.setNewTyperState().setTyper(new Typer(ctx.nestingLevel + 1)).setSource(source2)) { - val tree2 = new Parser(source2).block() - if ctx.reporter.allErrors.nonEmpty then - ctx.reporter.allErrors.map((ErrorKind.Parser, _)) - else - val tree3 = ctx.typer.typed(tree2) - ctx.base.postTyperPhase match - case postTyper: PostTyper if ctx.reporter.allErrors.isEmpty => - val tree4 = atPhase(postTyper) { postTyper.newTransformer.transform(tree3) } - ctx.base.inliningPhase match - case inlining: Inlining if ctx.reporter.allErrors.isEmpty => - atPhase(inlining) { inlining.newTransformer.transform(tree4) } - case _ => - case _ => - ctx.reporter.allErrors.map((ErrorKind.Typer, _)) - } - case t => - report.error(em"argument to compileError must be a statically known String but was: $codeArg", codeArg1.srcPos) - Nil - } - - private def packError(kind: ErrorKind, error: Error)(using Context): Tree = - def lit(x: Any) = Literal(Constant(x)) - val constructor: Tree = ref(defn.CompiletimeTesting_Error_apply) - val parserErrorKind: Tree = ref(defn.CompiletimeTesting_ErrorKind_Parser) - val typerErrorKind: Tree = ref(defn.CompiletimeTesting_ErrorKind_Typer) - - constructor.appliedTo( - lit(error.message), - lit(error.pos.lineContent.reverse.dropWhile("\n ".contains).reverse), - lit(error.pos.column), - if kind == ErrorKind.Parser then parserErrorKind else typerErrorKind) - - private def packErrors(errors: List[(ErrorKind, Error)], pos: SrcPos)(using Context): Tree = - val individualErrors: List[Tree] = errors.map(packError) - val errorTpt = ref(defn.CompiletimeTesting_ErrorClass).withSpan(pos.span) - mkList(individualErrors, errorTpt) - - /** Expand call to scala.compiletime.testing.typeChecks */ - def typeChecks(tree: Tree)(using Context): Tree = - val errors = compileForErrors(tree) - Literal(Constant(errors.isEmpty)).withSpan(tree.span) - - /** Expand call to scala.compiletime.testing.typeCheckErrors */ - def typeCheckErrors(tree: Tree)(using Context): Tree = - val errors = compileForErrors(tree) - packErrors(errors, tree) - - /** Expand call to scala.compiletime.codeOf */ - def codeOf(arg: Tree, pos: SrcPos)(using Context): Tree = - val ctx1 = ctx.fresh.setSetting(ctx.settings.color, "never") - Literal(Constant(arg.show(using ctx1))).withSpan(pos.span) - } - - extension (tp: Type) { - - /** same as widenTermRefExpr, but preserves modules and singleton enum values */ - private final def widenInlineScrutinee(using Context): Type = tp.stripTypeVar match { - case tp: TermRef => - val sym = tp.termSymbol - if sym.isAllOf(EnumCase, butNot=JavaDefined) || sym.is(Module) then tp - else if !tp.isOverloaded then tp.underlying.widenExpr.widenInlineScrutinee - else tp - case _ => tp - } - - } - - /** Very similar to TreeInfo.isPureExpr, but with the following inliner-only exceptions: - * - synthetic case class apply methods, when the case class constructor is empty, are - * elideable but not pure. Elsewhere, accessing the apply method might cause the initialization - * of a containing object so they are merely idempotent. - */ - object isElideableExpr { - def isStatElideable(tree: Tree)(using Context): Boolean = unsplice(tree) match { - case EmptyTree - | TypeDef(_, _) - | Import(_, _) - | DefDef(_, _, _, _) => - true - case vdef @ ValDef(_, _, _) => - if (vdef.symbol.flags is Mutable) false else apply(vdef.rhs) - case _ => - false - } - - def apply(tree: Tree)(using Context): Boolean = unsplice(tree) match { - case EmptyTree - | This(_) - | Super(_, _) - | Literal(_) => - true - case Ident(_) => - isPureRef(tree) || tree.symbol.isAllOf(InlineParam) - case Select(qual, _) => - if (tree.symbol.is(Erased)) true - else isPureRef(tree) && apply(qual) - case New(_) | Closure(_, _, _) => - true - case TypeApply(fn, _) => - if (fn.symbol.is(Erased) || fn.symbol == defn.QuotedTypeModule_of) true else apply(fn) - case Apply(fn, args) => - val isCaseClassApply = { - val cls = tree.tpe.classSymbol - val meth = fn.symbol - meth.name == nme.apply && - meth.flags.is(Synthetic) && - meth.owner.linkedClass.is(Case) && - cls.isNoInitsRealClass && - funPart(fn).match - case Select(qual, _) => qual.symbol.is(Synthetic) // e.g: disallow `{ ..; Foo }.apply(..)` - case meth @ Ident(_) => meth.symbol.is(Synthetic) // e.g: allow `import Foo.{ apply => foo }; foo(..)` - case _ => false - } - if isPureApply(tree, fn) then - apply(fn) && args.forall(apply) - else if (isCaseClassApply) - args.forall(apply) - else if (fn.symbol.is(Erased)) true - else false - case Typed(expr, _) => - apply(expr) - case Block(stats, expr) => - apply(expr) && stats.forall(isStatElideable) - case Inlined(_, bindings, expr) => - apply(expr) && bindings.forall(isStatElideable) - case NamedArg(_, expr) => - apply(expr) - case _ => - false - } - } -} - -/** Produces an inlined version of `call` via its `inlined` method. - * - * @param call the original call to an inlineable method - * @param rhsToInline the body of the inlineable method that replaces the call. - */ -class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { - import tpd._ - import Inliner._ - - private val methPart = funPart(call) - private val callTypeArgs = typeArgss(call).flatten - private val callValueArgss = termArgss(call) - private val inlinedMethod = methPart.symbol - private val inlineCallPrefix = - qualifier(methPart).orElse(This(inlinedMethod.enclosingClass.asClass)) - - inlining.println(i"-----------------------\nInlining $call\nWith RHS $rhsToInline") - - // Make sure all type arguments to the call are fully determined, - // but continue if that's not achievable (or else i7459.scala would crash). - for arg <- callTypeArgs do - isFullyDefined(arg.tpe, ForceDegree.flipBottom) - - /** A map from parameter names of the inlineable method to references of the actual arguments. - * For a type argument this is the full argument type. - * For a value argument, it is a reference to either the argument value - * (if the argument is a pure expression of singleton type), or to `val` or `def` acting - * as a proxy (if the argument is something else). - */ - private val paramBinding = new mutable.HashMap[Name, Type] - - /** A map from parameter names of the inlineable method to spans of the actual arguments */ - private val paramSpan = new mutable.HashMap[Name, Span] - - /** A map from references to (type and value) parameters of the inlineable method - * to their corresponding argument or proxy references, as given by `paramBinding`. - */ - private val paramProxy = new mutable.HashMap[Type, Type] - - /** A map from the classes of (direct and outer) this references in `rhsToInline` - * to references of their proxies. - * Note that we can't index by the ThisType itself since there are several - * possible forms to express what is logicaly the same ThisType. E.g. - * - * ThisType(TypeRef(ThisType(p), cls)) - * - * vs - * - * ThisType(TypeRef(TermRef(ThisType(), p), cls)) - * - * These are different (wrt ==) types but represent logically the same key - */ - private val thisProxy = new mutable.HashMap[ClassSymbol, TermRef] - - /** A buffer for bindings that define proxies for actual arguments */ - private val bindingsBuf = new mutable.ListBuffer[ValOrDefDef] - - private def newSym(name: Name, flags: FlagSet, info: Type)(using Context): Symbol = - newSymbol(ctx.owner, name, flags, info, coord = call.span) - - /** A binding for the parameter of an inline method. This is a `val` def for - * by-value parameters and a `def` def for by-name parameters. `val` defs inherit - * inline annotations from their parameters. The generated `def` is appended - * to `buf`. - * @param name the name of the parameter - * @param formal the type of the parameter - * @param arg the argument corresponding to the parameter - * @param buf the buffer to which the definition should be appended - */ - private def paramBindingDef(name: Name, formal: Type, arg0: Tree, - buf: DefBuffer)(using Context): ValOrDefDef = { - val isByName = formal.dealias.isInstanceOf[ExprType] - val arg = arg0 match { - case Typed(arg1, tpt) if tpt.tpe.isRepeatedParam && arg1.tpe.derivesFrom(defn.ArrayClass) => - wrapArray(arg1, arg0.tpe.elemType) - case _ => arg0 - } - val argtpe = arg.tpe.dealiasKeepAnnots.translateFromRepeated(toArray = false) - val argIsBottom = argtpe.isBottomTypeAfterErasure - val bindingType = - if argIsBottom then formal - else if isByName then ExprType(argtpe.widen) - else argtpe.widen - var bindingFlags: FlagSet = InlineProxy - if formal.widenExpr.hasAnnotation(defn.InlineParamAnnot) then - bindingFlags |= Inline - if formal.widenExpr.hasAnnotation(defn.ErasedParamAnnot) then - bindingFlags |= Erased - if isByName then - bindingFlags |= Method - val boundSym = newSym(InlineBinderName.fresh(name.asTermName), bindingFlags, bindingType).asTerm - val binding = { - var newArg = arg.changeOwner(ctx.owner, boundSym) - if bindingFlags.is(Inline) && argIsBottom then - newArg = Typed(newArg, TypeTree(formal)) // type ascribe RHS to avoid type errors in expansion. See i8612.scala - if isByName then DefDef(boundSym, newArg) - else ValDef(boundSym, newArg) - }.withSpan(boundSym.span) - inlining.println(i"parameter binding: $binding, $argIsBottom") - buf += binding - binding - } - - /** Populate `paramBinding` and `buf` by matching parameters with - * corresponding arguments. `bindingbuf` will be further extended later by - * proxies to this-references. Issue an error if some arguments are missing. - */ - private def computeParamBindings( - tp: Type, targs: List[Tree], - argss: List[List[Tree]], formalss: List[List[Type]], - buf: DefBuffer): Boolean = - tp match - case tp: PolyType => - tp.paramNames.lazyZip(targs).foreach { (name, arg) => - paramSpan(name) = arg.span - paramBinding(name) = arg.tpe.stripTypeVar - } - computeParamBindings(tp.resultType, targs.drop(tp.paramNames.length), argss, formalss, buf) - case tp: MethodType => - if argss.isEmpty then - report.error(i"missing arguments for inline method $inlinedMethod", call.srcPos) - false - else - tp.paramNames.lazyZip(formalss.head).lazyZip(argss.head).foreach { (name, formal, arg) => - paramSpan(name) = arg.span - paramBinding(name) = arg.tpe.dealias match - case _: SingletonType if isIdempotentPath(arg) => - arg.tpe - case _ => - paramBindingDef(name, formal, arg, buf).symbol.termRef - } - computeParamBindings(tp.resultType, targs, argss.tail, formalss.tail, buf) - case _ => - assert(targs.isEmpty) - assert(argss.isEmpty) - true - - // Compute val-definitions for all this-proxies and append them to `bindingsBuf` - private def computeThisBindings() = { - // All needed this-proxies, paired-with and sorted-by nesting depth of - // the classes they represent (innermost first) - val sortedProxies = thisProxy.toList - .map((cls, proxy) => (cls.ownersIterator.length, proxy.symbol)) - .sortBy(-_._1) - - var lastSelf: Symbol = NoSymbol - var lastLevel: Int = 0 - for ((level, selfSym) <- sortedProxies) { - val rhs = selfSym.info.dealias match - case info: TermRef - if info.isStable && (lastSelf.exists || isPureExpr(inlineCallPrefix)) => - // If this is the first proxy, optimize to `ref(info)` only if call prefix is pure. - // Otherwise we might forget side effects. See run/i12829.scala. - ref(info) - case info => - val rhsClsSym = info.widenDealias.classSymbol - if rhsClsSym.is(Module) && rhsClsSym.isStatic then - ref(rhsClsSym.sourceModule) - else if lastSelf.exists then - ref(lastSelf).outerSelect(lastLevel - level, selfSym.info) - else - val pre = inlineCallPrefix match - case Super(qual, _) => qual - case pre => pre - val preLevel = inlinedMethod.owner.ownersIterator.length - if preLevel > level then pre.outerSelect(preLevel - level, selfSym.info) - else pre - - val binding = accountForOpaques( - ValDef(selfSym.asTerm, QuoteUtils.changeOwnerOfTree(rhs, selfSym)).withSpan(selfSym.span)) - bindingsBuf += binding - inlining.println(i"proxy at $level: $selfSym = ${bindingsBuf.last}") - lastSelf = selfSym - lastLevel = level - } - } - - /** A list of pairs between TermRefs appearing in thisProxy bindings that - * refer to objects with opaque type aliases and local proxy symbols - * that contain refined versions of these TermRefs where the aliases - * are exposed. - */ - private val opaqueProxies = new mutable.ListBuffer[(TermRef, TermRef)] - - /** Map first halfs of opaqueProxies pairs to second halfs, using =:= as equality */ - def mapRef(ref: TermRef): Option[TermRef] = - opaqueProxies.collectFirst { - case (from, to) if from.symbol == ref.symbol && from =:= ref => to - } - - /** If `tp` contains TermRefs that refer to objects with opaque - * type aliases, add proxy definitions to `opaqueProxies` that expose these aliases. - */ - def addOpaqueProxies(tp: Type, span: Span, forThisProxy: Boolean)(using Context): Unit = - tp.foreachPart { - case ref: TermRef => - for cls <- ref.widen.baseClasses do - if cls.containsOpaques - && (forThisProxy || inlinedMethod.isContainedIn(cls)) - && mapRef(ref).isEmpty - then - def openOpaqueAliases(selfType: Type): List[(Name, Type)] = selfType match - case RefinedType(parent, rname, TypeAlias(alias)) => - val opaq = cls.info.member(rname).symbol - if opaq.isOpaqueAlias then - (rname, alias.stripLazyRef.asSeenFrom(ref, cls)) - :: openOpaqueAliases(parent) - else Nil - case _ => - Nil - val refinements = openOpaqueAliases(cls.givenSelfType) - val refinedType = refinements.foldLeft(ref: Type) ((parent, refinement) => - RefinedType(parent, refinement._1, TypeAlias(refinement._2)) - ) - val refiningSym = newSym(InlineBinderName.fresh(), Synthetic, refinedType).asTerm - val refiningDef = ValDef(refiningSym, tpd.ref(ref).cast(refinedType)).withSpan(span) - inlining.println(i"add opaque alias proxy $refiningDef for $ref in $tp") - bindingsBuf += refiningDef - opaqueProxies += ((ref, refiningSym.termRef)) - case _ => - } - - /** Map all TermRefs that match left element in `opaqueProxies` to the - * corresponding right element. - */ - val mapOpaques = TreeTypeMap( - typeMap = new TypeMap: - override def stopAt = StopAt.Package - def apply(t: Type) = mapOver { - t match - case ref: TermRef => mapRef(ref).getOrElse(ref) - case _ => t - } - ) - - /** If `binding` contains TermRefs that refer to objects with opaque - * type aliases, add proxy definitions that expose these aliases - * and substitute such TermRefs with theproxies. Example from pos/opaque-inline1.scala: - * - * object refined: - * opaque type Positive = Int - * inline def Positive(value: Int): Positive = f(value) - * def f(x: Positive): Positive = x - * def run: Unit = { val x = 9; val nine = refined.Positive(x) } - * - * This generates the following proxies: - * - * val $proxy1: refined.type{type Positive = Int} = - * refined.$asInstanceOf$[refined.type{type Positive = Int}] - * val refined$_this: ($proxy1 : refined.type{Positive = Int}) = - * $proxy1 - * - * and every reference to `refined` in the inlined expression is replaced by - * `refined_$this`. - */ - def accountForOpaques(binding: ValDef)(using Context): ValDef = - addOpaqueProxies(binding.symbol.info, binding.span, forThisProxy = true) - if opaqueProxies.isEmpty then binding - else - binding.symbol.info = mapOpaques.typeMap(binding.symbol.info) - mapOpaques.transform(binding).asInstanceOf[ValDef] - .showing(i"transformed this binding exposing opaque aliases: $result", inlining) - end accountForOpaques - - /** If value argument contains references to objects that contain opaque types, - * map them to their opaque proxies. - */ - def mapOpaquesInValueArg(arg: Tree)(using Context): Tree = - val argType = arg.tpe.widenDealias - addOpaqueProxies(argType, arg.span, forThisProxy = false) - if opaqueProxies.nonEmpty then - val mappedType = mapOpaques.typeMap(argType) - if mappedType ne argType then arg.cast(AndType(arg.tpe, mappedType)) - else arg - else arg - - private def canElideThis(tpe: ThisType): Boolean = - inlineCallPrefix.tpe == tpe && ctx.owner.isContainedIn(tpe.cls) - || tpe.cls.isContainedIn(inlinedMethod) - || tpe.cls.is(Package) - || tpe.cls.isStaticOwner && !(tpe.cls.seesOpaques && inlinedMethod.isContainedIn(tpe.cls)) - - /** Populate `thisProxy` and `paramProxy` as follows: - * - * 1a. If given type refers to a static this, thisProxy binds it to corresponding global reference, - * 1b. If given type refers to an instance this to a class that is not contained in the - * inline method, create a proxy symbol and bind the thistype to refer to the proxy. - * The proxy is not yet entered in `bindingsBuf`; that will come later. - * 2. If given type refers to a parameter, make `paramProxy` refer to the entry stored - * in `paramNames` under the parameter's name. This roundabout way to bind parameter - * references to proxies is done because we don't know a priori what the parameter - * references of a method are (we only know the method's type, but that contains TypeParamRefs - * and MethodParams, not TypeRefs or TermRefs. - */ - private def registerType(tpe: Type): Unit = tpe match { - case tpe: ThisType if !canElideThis(tpe) && !thisProxy.contains(tpe.cls) => - val proxyName = s"${tpe.cls.name}_this".toTermName - def adaptToPrefix(tp: Type) = tp.asSeenFrom(inlineCallPrefix.tpe, inlinedMethod.owner) - val proxyType = inlineCallPrefix.tpe.dealias.tryNormalize match { - case typeMatchResult if typeMatchResult.exists => typeMatchResult - case _ => adaptToPrefix(tpe).widenIfUnstable - } - thisProxy(tpe.cls) = newSym(proxyName, InlineProxy, proxyType).termRef - if (!tpe.cls.isStaticOwner) - registerType(inlinedMethod.owner.thisType) // make sure we have a base from which to outer-select - for (param <- tpe.cls.typeParams) - paramProxy(param.typeRef) = adaptToPrefix(param.typeRef) - case tpe: NamedType - if tpe.symbol.is(Param) - && tpe.symbol.owner == inlinedMethod - && (tpe.symbol.isTerm || inlinedMethod.paramSymss.exists(_.contains(tpe.symbol))) - // this test is needed to rule out nested LambdaTypeTree parameters - // with the same name as the method's parameters. Note that the nested - // LambdaTypeTree parameters also have the inlineMethod as owner. C.f. i13460.scala. - && !paramProxy.contains(tpe) => - paramBinding.get(tpe.name) match - case Some(bound) => paramProxy(tpe) = bound - case _ => // can happen for params bound by type-lambda trees. - - // The widened type may contain param types too (see tests/pos/i12379a.scala) - if tpe.isTerm then registerType(tpe.widenTermRefExpr) - case _ => - } - - private val registerTypes = new TypeTraverser: - override def stopAt = StopAt.Package - override def traverse(t: Type) = - registerType(t) - traverseChildren(t) - - /** Register type of leaf node */ - private def registerLeaf(tree: Tree): Unit = tree match - case _: This | _: Ident | _: TypeTree => registerTypes.traverse(tree.typeOpt) - case _ => - - /** Make `tree` part of inlined expansion. This means its owner has to be changed - * from its `originalOwner`, and, if it comes from outside the inlined method - * itself, it has to be marked as an inlined argument. - */ - def integrate(tree: Tree, originalOwner: Symbol)(using Context): Tree = - // assertAllPositioned(tree) // debug - tree.changeOwner(originalOwner, ctx.owner) - - def tryConstValue: Tree = - TypeComparer.constValue(callTypeArgs.head.tpe) match { - case Some(c) => Literal(c).withSpan(call.span) - case _ => EmptyTree - } - - /** The Inlined node representing the inlined call */ - def inlined(sourcePos: SrcPos): Tree = { - - // Special handling of `requireConst` and `codeOf` - callValueArgss match - case (arg :: Nil) :: Nil => - if inlinedMethod == defn.Compiletime_requireConst then - arg match - case ConstantValue(_) | Inlined(_, Nil, Typed(ConstantValue(_), _)) => // ok - case _ => report.error(em"expected a constant value but found: $arg", arg.srcPos) - return Literal(Constant(())).withSpan(sourcePos.span) - else if inlinedMethod == defn.Compiletime_codeOf then - return Intrinsics.codeOf(arg, call.srcPos) - case _ => - - // Special handling of `constValue[T]`, `constValueOpt[T], and summonInline[T]` - if (callTypeArgs.length == 1) - if (inlinedMethod == defn.Compiletime_constValue) { - val constVal = tryConstValue - if constVal.isEmpty then - val msg = em"not a constant type: ${callTypeArgs.head}; cannot take constValue" - return ref(defn.Predef_undefined).withSpan(call.span).withType(ErrorType(msg)) - else - return constVal - } - else if (inlinedMethod == defn.Compiletime_constValueOpt) { - val constVal = tryConstValue - return ( - if (constVal.isEmpty) ref(defn.NoneModule.termRef) - else New(defn.SomeClass.typeRef.appliedTo(constVal.tpe), constVal :: Nil) - ) - } - else if (inlinedMethod == defn.Compiletime_summonInline) { - def searchImplicit(tpt: Tree) = - val evTyper = new Typer(ctx.nestingLevel + 1) - val evCtx = ctx.fresh.setTyper(evTyper) - inContext(evCtx) { - val evidence = evTyper.inferImplicitArg(tpt.tpe, tpt.span) - evidence.tpe match - case fail: Implicits.SearchFailureType => - val msg = evTyper.missingArgMsg(evidence, tpt.tpe, "") - errorTree(call, em"$msg") - case _ => - evidence - } - return searchImplicit(callTypeArgs.head) - } - - def paramTypess(call: Tree, acc: List[List[Type]]): List[List[Type]] = call match - case Apply(fn, args) => - fn.tpe.widen.match - case mt: MethodType => paramTypess(fn, mt.instantiateParamInfos(args.tpes) :: acc) - case _ => Nil - case TypeApply(fn, _) => paramTypess(fn, acc) - case _ => acc - - val paramBindings = - val mappedCallValueArgss = callValueArgss.nestedMapConserve(mapOpaquesInValueArg) - if mappedCallValueArgss ne callValueArgss then - inlining.println(i"mapped value args = ${mappedCallValueArgss.flatten}%, %") - - val paramBindingsBuf = new DefBuffer - // Compute bindings for all parameters, appending them to bindingsBuf - if !computeParamBindings( - inlinedMethod.info, callTypeArgs, - mappedCallValueArgss, paramTypess(call, Nil), - paramBindingsBuf) - then - return call - - paramBindingsBuf.toList - end paramBindings - - // make sure prefix is executed if it is impure - if !isIdempotentExpr(inlineCallPrefix) then registerType(inlinedMethod.owner.thisType) - - // Register types of all leaves of inlined body so that the `paramProxy` and `thisProxy` maps are defined. - rhsToInline.foreachSubTree(registerLeaf) - - // Compute bindings for all this-proxies, appending them to bindingsBuf - computeThisBindings() - - // Parameter bindings come after this bindings, reflecting order of evaluation - bindingsBuf ++= paramBindings - - val inlineTyper = new InlineTyper(ctx.reporter.errorCount) - - val inlineCtx = inlineContext(call).fresh.setTyper(inlineTyper).setNewScope - - def inlinedFromOutside(tree: Tree)(span: Span): Tree = - Inlined(EmptyTree, Nil, tree)(using ctx.withSource(inlinedMethod.topLevelClass.source)).withSpan(span) - - // InlineCopier is a more fault-tolerant copier that does not cause errors when - // function types in applications are undefined. This is necessary since we copy at - // the same time as establishing the proper context in which the copied tree should - // be evaluated. This matters for opaque types, see neg/i14653.scala. - class InlineCopier() extends TypedTreeCopier: - override def Apply(tree: Tree)(fun: Tree, args: List[Tree])(using Context): Apply = - if fun.tpe.widen.exists then super.Apply(tree)(fun, args) - else untpd.cpy.Apply(tree)(fun, args).withTypeUnchecked(tree.tpe) - - // InlinerMap is a TreeTypeMap with special treatment for inlined arguments: - // They are generally left alone (not mapped further, and if they wrap a type - // the type Inlined wrapper gets dropped - class InlinerMap( - typeMap: Type => Type, - treeMap: Tree => Tree, - oldOwners: List[Symbol], - newOwners: List[Symbol], - substFrom: List[Symbol], - substTo: List[Symbol])(using Context) - extends TreeTypeMap( - typeMap, treeMap, oldOwners, newOwners, substFrom, substTo, InlineCopier()): - - override def copy( - typeMap: Type => Type, - treeMap: Tree => Tree, - oldOwners: List[Symbol], - newOwners: List[Symbol], - substFrom: List[Symbol], - substTo: List[Symbol])(using Context) = - new InlinerMap(typeMap, treeMap, oldOwners, newOwners, substFrom, substTo) - - override def transformInlined(tree: Inlined)(using Context) = - if tree.call.isEmpty then - tree.expansion match - case expansion: TypeTree => expansion - case _ => tree - else super.transformInlined(tree) - end InlinerMap - - // A tree type map to prepare the inlined body for typechecked. - // The translation maps references to `this` and parameters to - // corresponding arguments or proxies on the type and term level. It also changes - // the owner from the inlined method to the current owner. - val inliner = new InlinerMap( - typeMap = - new DeepTypeMap { - override def stopAt = - if opaqueProxies.isEmpty then StopAt.Static else StopAt.Package - def apply(t: Type) = t match { - case t: ThisType => thisProxy.getOrElse(t.cls, t) - case t: TypeRef => paramProxy.getOrElse(t, mapOver(t)) - case t: SingletonType => - if t.termSymbol.isAllOf(InlineParam) then apply(t.widenTermRefExpr) - else paramProxy.getOrElse(t, mapOver(t)) - case t => mapOver(t) - } - }, - treeMap = { - case tree: This => - tree.tpe match { - case thistpe: ThisType => - thisProxy.get(thistpe.cls) match { - case Some(t) => - val thisRef = ref(t).withSpan(call.span) - inlinedFromOutside(thisRef)(tree.span) - case None => tree - } - case _ => tree - } - case tree: Ident => - /* Span of the argument. Used when the argument is inlined directly without a binding */ - def argSpan = - if (tree.name == nme.WILDCARD) tree.span // From type match - else if (tree.symbol.isTypeParam && tree.symbol.owner.isClass) tree.span // TODO is this the correct span? - else paramSpan(tree.name) - val inlinedCtx = ctx.withSource(inlinedMethod.topLevelClass.source) - paramProxy.get(tree.tpe) match { - case Some(t) if tree.isTerm && t.isSingleton => - val inlinedSingleton = singleton(t).withSpan(argSpan) - inlinedFromOutside(inlinedSingleton)(tree.span) - case Some(t) if tree.isType => - inlinedFromOutside(TypeTree(t).withSpan(argSpan))(tree.span) - case _ => tree - } - case tree @ Select(qual: This, name) if tree.symbol.is(Private) && tree.symbol.isInlineMethod => - // This inline method refers to another (private) inline method (see tests/pos/i14042.scala). - // We insert upcast to access the private inline method once inlined. This makes the selection - // keep the symbol when re-typechecking in the InlineTyper. The method is inlined and hence no - // reference to a private method is kept at runtime. - cpy.Select(tree)(qual.asInstance(qual.tpe.widen), name) - - case tree => tree - }, - oldOwners = inlinedMethod :: Nil, - newOwners = ctx.owner :: Nil, - substFrom = Nil, - substTo = Nil - )(using inlineCtx) - - inlining.println( - i"""inliner transform with - |thisProxy = ${thisProxy.toList.map(_._1)}%, % --> ${thisProxy.toList.map(_._2)}%, % - |paramProxy = ${paramProxy.toList.map(_._1.typeSymbol.showLocated)}%, % --> ${paramProxy.toList.map(_._2)}%, %""") - - // Apply inliner to `rhsToInline`, split off any implicit bindings from result, and - // make them part of `bindingsBuf`. The expansion is then the tree that remains. - val expansion = inliner.transform(rhsToInline) - - def issueError() = callValueArgss match { - case (msgArg :: Nil) :: Nil => - val message = msgArg.tpe match { - case ConstantType(Constant(msg: String)) => msg - case _ => s"A literal string is expected as an argument to `compiletime.error`. Got ${msgArg.show}" - } - // Usually `error` is called from within a rewrite method. In this - // case we need to report the error at the point of the outermost enclosing inline - // call. This way, a defensively written rewrite method can always - // report bad inputs at the point of call instead of revealing its internals. - val callToReport = if (enclosingInlineds.nonEmpty) enclosingInlineds.last else call - val ctxToReport = ctx.outersIterator.dropWhile(enclosingInlineds(using _).nonEmpty).next - // The context in which we report should still use the existing context reporter - val ctxOrigReporter = ctxToReport.fresh.setReporter(ctx.reporter) - inContext(ctxOrigReporter) { - report.error(message, callToReport.srcPos) - } - case _ => - } - - /** The number of nodes in this tree, excluding code in nested inline - * calls and annotations of definitions. - */ - def treeSize(x: Any): Int = - var siz = 0 - x match - case x: Trees.Inlined[_] => - case x: Positioned => - var i = 0 - while i < x.productArity do - siz += treeSize(x.productElement(i)) - i += 1 - case x: List[_] => - var xs = x - while xs.nonEmpty do - siz += treeSize(xs.head) - xs = xs.tail - case _ => - siz - - trace(i"inlining $call", inlining, show = true) { - - // The normalized bindings collected in `bindingsBuf` - bindingsBuf.mapInPlace { binding => - // Set trees to symbols allow macros to see the definition tree. - // This is used by `underlyingArgument`. - val binding1 = reducer.normalizeBinding(binding)(using inlineCtx).setDefTree - binding1.foreachSubTree { - case tree: MemberDef => tree.setDefTree - case _ => - } - binding1 - } - - // Run a typing pass over the inlined tree. See InlineTyper for details. - val expansion1 = inlineTyper.typed(expansion)(using inlineCtx) - - if (ctx.settings.verbose.value) { - inlining.println(i"to inline = $rhsToInline") - inlining.println(i"original bindings = ${bindingsBuf.toList}%\n%") - inlining.println(i"original expansion = $expansion1") - } - - // Drop unused bindings - val (finalBindings, finalExpansion) = dropUnusedDefs(bindingsBuf.toList, expansion1) - - if (inlinedMethod == defn.Compiletime_error) issueError() - - addInlinedTrees(treeSize(finalExpansion)) - - // Take care that only argument bindings go into `bindings`, since positions are - // different for bindings from arguments and bindings from body. - val res = tpd.Inlined(call, finalBindings, finalExpansion) - if opaqueProxies.isEmpty then res - else - val target = - if inlinedMethod.is(Transparent) then call.tpe & res.tpe - else call.tpe - res.ensureConforms(target) - // Make sure that the sealing with the declared type - // is type correct. Without it we might get problems since the - // expression's type is the opaque alias but the call's type is - // the opaque type itself. An example is in pos/opaque-inline1.scala. - } - } - - /** A utility object offering methods for rewriting inlined code */ - object reducer { - - /** An extractor for terms equivalent to `new C(args)`, returning the class `C`, - * a list of bindings, and the arguments `args`. Can see inside blocks and Inlined nodes and can - * follow a reference to an inline value binding to its right hand side. - * - * @return optionally, a triple consisting of - * - the class `C` - * - the arguments `args` - * - any bindings that wrap the instance creation - * - whether the instance creation is precomputed or by-name - */ - private object NewInstance { - def unapply(tree: Tree)(using Context): Option[(Symbol, List[Tree], List[Tree], Boolean)] = { - def unapplyLet(bindings: List[Tree], expr: Tree) = - unapply(expr) map { - case (cls, reduced, prefix, precomputed) => (cls, reduced, bindings ::: prefix, precomputed) - } - tree match { - case Apply(fn, args) => - fn match { - case Select(New(tpt), nme.CONSTRUCTOR) => - Some((tpt.tpe.classSymbol, args, Nil, false)) - case TypeApply(Select(New(tpt), nme.CONSTRUCTOR), _) => - Some((tpt.tpe.classSymbol, args, Nil, false)) - case _ => - val meth = fn.symbol - if (meth.name == nme.apply && - meth.flags.is(Synthetic) && - meth.owner.linkedClass.is(Case)) - Some(meth.owner.linkedClass, args, Nil, false) - else None - } - case Typed(inner, _) => - // drop the ascribed tpt. We only need it if we can't find a NewInstance - unapply(inner) - case Ident(_) => - val binding = tree.symbol.defTree - for ((cls, reduced, prefix, precomputed) <- unapply(binding)) - yield (cls, reduced, prefix, precomputed || binding.isInstanceOf[ValDef]) - case Inlined(_, bindings, expansion) => - unapplyLet(bindings, expansion) - case Block(stats, expr) if isElideableExpr(tree) => - unapplyLet(stats, expr) - case _ => - None - } - } - } - - /** If `tree` is equivalent to `new C(args).x` where class `C` does not have - * initialization code and `x` is a parameter corresponding to one of the - * arguments `args`, the corresponding argument, otherwise `tree` itself. - * Side effects of original arguments need to be preserved. - */ - def reduceProjection(tree: Tree)(using Context): Tree = { - if (ctx.debug) inlining.println(i"try reduce projection $tree") - tree match { - case Select(NewInstance(cls, args, prefix, precomputed), field) if cls.isNoInitsRealClass => - def matches(param: Symbol, selection: Symbol): Boolean = - param == selection || { - selection.name match { - case InlineAccessorName(underlying) => - param.name == underlying && selection.info.isInstanceOf[ExprType] - case _ => - false - } - } - val idx = cls.asClass.paramAccessors.indexWhere(matches(_, tree.symbol)) - if (idx >= 0 && idx < args.length) { - def finish(arg: Tree) = - new TreeTypeMap().transform(arg) // make sure local bindings in argument have fresh symbols - .showing(i"projecting $tree -> $result", inlining) - val arg = args(idx) - if (precomputed) - if (isElideableExpr(arg)) finish(arg) - else tree // nothing we can do here, projection would duplicate side effect - else { - // newInstance is evaluated in place, need to reflect side effects of - // arguments in the order they were written originally - def collectImpure(from: Int, end: Int) = - (from until end).filterNot(i => isElideableExpr(args(i))).toList.map(args) - val leading = collectImpure(0, idx) - val trailing = collectImpure(idx + 1, args.length) - val argInPlace = - if (trailing.isEmpty) arg - else - def argsSpan = trailing.map(_.span).foldLeft(arg.span)(_.union(_)) - letBindUnless(TreeInfo.Pure, arg)(Block(trailing, _).withSpan(argsSpan)) - val blockSpan = (prefix ::: leading).map(_.span).foldLeft(argInPlace.span)(_.union(_)) - finish(seq(prefix, seq(leading, argInPlace)).withSpan(blockSpan)) - } - } - else tree - case Block(stats, expr) if stats.forall(isPureBinding) => - cpy.Block(tree)(stats, reduceProjection(expr)) - case _ => tree - } - } - - /** If this is a value binding: - * - reduce its rhs if it is a projection and adjust its type accordingly, - * - record symbol -> rhs in the InlineBindings context propery. - */ - def normalizeBinding(binding: ValOrDefDef)(using Context) = { - val binding1 = binding match { - case binding: ValDef => - val rhs1 = reduceProjection(binding.rhs) - binding.symbol.defTree = rhs1 - if (rhs1 `eq` binding.rhs) binding - else { - binding.symbol.info = rhs1.tpe - cpy.ValDef(binding)(tpt = TypeTree(rhs1.tpe), rhs = rhs1) - } - case _ => - binding - } - binding1.withSpan(call.span) - } - - /** An extractor for references to inlineable arguments. These are : - * - by-value arguments marked with `inline` - * - all by-name arguments - */ - private object InlineableArg { - lazy val paramProxies = paramProxy.values.toSet - def unapply(tree: Trees.Ident[?])(using Context): Option[Tree] = { - def search(buf: DefBuffer) = buf.find(_.name == tree.name) - if (paramProxies.contains(tree.typeOpt)) - search(bindingsBuf) match { - case Some(bind: ValOrDefDef) if bind.symbol.is(Inline) => - Some(integrate(bind.rhs, bind.symbol)) - case _ => None - } - else None - } - } - - def tryInlineArg(tree: Tree)(using Context): Tree = tree match { - case InlineableArg(rhs) => - inlining.println(i"inline arg $tree -> $rhs") - rhs - case _ => - EmptyTree - } - - /** Rewrite an application - * - * ((x1, ..., xn) => b)(e1, ..., en) - * - * to - * - * val/def x1 = e1; ...; val/def xn = en; b - * - * where `def` is used for call-by-name parameters. However, we shortcut any NoPrefix - * refs among the ei's directly without creating an intermediate binding. - */ - def betaReduce(tree: Tree)(using Context): Tree = tree match { - case Apply(Select(cl @ closureDef(ddef), nme.apply), args) if defn.isFunctionType(cl.tpe) => - // closureDef also returns a result for closures wrapped in Inlined nodes. - // These need to be preserved. - def recur(cl: Tree): Tree = cl match - case Inlined(call, bindings, expr) => - cpy.Inlined(cl)(call, bindings, recur(expr)) - case _ => ddef.tpe.widen match - case mt: MethodType if ddef.paramss.head.length == args.length => - val bindingsBuf = new DefBuffer - val argSyms = mt.paramNames.lazyZip(mt.paramInfos).lazyZip(args).map { (name, paramtp, arg) => - arg.tpe.dealias match { - case ref @ TermRef(NoPrefix, _) => ref.symbol - case _ => - paramBindingDef(name, paramtp, arg, bindingsBuf)( - using ctx.withSource(cl.source) - ).symbol - } - } - val expander = new TreeTypeMap( - oldOwners = ddef.symbol :: Nil, - newOwners = ctx.owner :: Nil, - substFrom = ddef.paramss.head.map(_.symbol), - substTo = argSyms) - Block(bindingsBuf.toList, expander.transform(ddef.rhs)).withSpan(tree.span) - case _ => tree - recur(cl) - case _ => tree - } - - /** The result type of reducing a match. It consists optionally of a list of bindings - * for the pattern-bound variables and the RHS of the selected case. - * Returns `None` if no case was selected. - */ - type MatchRedux = Option[(List[MemberDef], Tree)] - - /** Same as MatchRedux, but also includes a boolean - * that is true if the guard can be checked at compile time. - */ - type MatchReduxWithGuard = Option[(List[MemberDef], Tree, Boolean)] - - /** Reduce an inline match - * @param mtch the match tree - * @param scrutinee the scrutinee expression, assumed to be pure, or - * EmptyTree for a summonFrom - * @param scrutType its fully defined type, or - * ImplicitScrutineeTypeRef for a summonFrom - * @param typer The current inline typer - * @return optionally, if match can be reduced to a matching case: A pair of - * bindings for all pattern-bound variables and the RHS of the case. - */ - def reduceInlineMatch(scrutinee: Tree, scrutType: Type, cases: List[CaseDef], typer: Typer)(using Context): MatchRedux = { - - val isImplicit = scrutinee.isEmpty - - /** Try to match pattern `pat` against scrutinee reference `scrut`. If successful add - * bindings for variables bound in this pattern to `caseBindingMap`. - */ - def reducePattern( - caseBindingMap: mutable.ListBuffer[(Symbol, MemberDef)], - scrut: TermRef, - pat: Tree - )(using Context): Boolean = { - - /** Create a binding of a pattern bound variable with matching part of - * scrutinee as RHS and type that corresponds to RHS. - */ - def newTermBinding(sym: TermSymbol, rhs: Tree): Unit = { - val copied = sym.copy(info = rhs.tpe.widenInlineScrutinee, coord = sym.coord, flags = sym.flags &~ Case).asTerm - caseBindingMap += ((sym, ValDef(copied, constToLiteral(rhs)).withSpan(sym.span))) - } - - def newTypeBinding(sym: TypeSymbol, alias: Type): Unit = { - val copied = sym.copy(info = TypeAlias(alias), coord = sym.coord).asType - caseBindingMap += ((sym, TypeDef(copied))) - } - - def searchImplicit(sym: TermSymbol, tpt: Tree) = { - val evTyper = new Typer(ctx.nestingLevel + 1) - val evCtx = ctx.fresh.setTyper(evTyper) - inContext(evCtx) { - val evidence = evTyper.inferImplicitArg(tpt.tpe, tpt.span) - evidence.tpe match { - case fail: Implicits.AmbiguousImplicits => - report.error(evTyper.missingArgMsg(evidence, tpt.tpe, ""), tpt.srcPos) - true // hard error: return true to stop implicit search here - case fail: Implicits.SearchFailureType => - false - case _ => - //inlining.println(i"inferred implicit $sym: ${sym.info} with $evidence: ${evidence.tpe.widen}, ${evCtx.gadt.constraint}, ${evCtx.typerState.constraint}") - newTermBinding(sym, evidence) - true - } - } - } - - type TypeBindsMap = SimpleIdentityMap[TypeSymbol, java.lang.Boolean] - - def getTypeBindsMap(pat: Tree, tpt: Tree): TypeBindsMap = { - val getBinds = new TreeAccumulator[Set[TypeSymbol]] { - def apply(syms: Set[TypeSymbol], t: Tree)(using Context): Set[TypeSymbol] = { - val syms1 = t match { - case t: Bind if t.symbol.isType => - syms + t.symbol.asType - case _ => syms - } - foldOver(syms1, t) - } - } - - // Extractors contain Bind nodes in type parameter lists, the tree looks like this: - // UnApply[t @ t](pats)(implicits): T[t] - // Test case is pos/inline-caseclass.scala. - val binds: Set[TypeSymbol] = pat match { - case UnApply(TypeApply(_, tpts), _, _) => getBinds(Set.empty[TypeSymbol], tpts) - case _ => getBinds(Set.empty[TypeSymbol], tpt) - } - - val extractBindVariance = new TypeAccumulator[TypeBindsMap] { - def apply(syms: TypeBindsMap, t: Type) = { - val syms1 = t match { - // `binds` is used to check if the symbol was actually bound by the pattern we're processing - case tr: TypeRef if tr.symbol.is(Case) && binds.contains(tr.symbol.asType) => - val trSym = tr.symbol.asType - // Exact same logic as in IsFullyDefinedAccumulator: - // the binding is to be maximized iff it only occurs contravariantly in the type - val wasToBeMinimized: Boolean = { - val v = syms(trSym) - if (v != null) v else false - } - syms.updated(trSym, wasToBeMinimized || variance >= 0 : java.lang.Boolean) - case _ => - syms - } - foldOver(syms1, t) - } - } - - extractBindVariance(SimpleIdentityMap.empty, tpt.tpe) - } - - def addTypeBindings(typeBinds: TypeBindsMap)(using Context): Unit = - typeBinds.foreachBinding { case (sym, shouldBeMinimized) => - newTypeBinding(sym, ctx.gadt.approximation(sym, fromBelow = shouldBeMinimized)) - } - - def registerAsGadtSyms(typeBinds: TypeBindsMap)(using Context): Unit = - if (typeBinds.size > 0) ctx.gadt.addToConstraint(typeBinds.keys) - - pat match { - case Typed(pat1, tpt) => - val typeBinds = getTypeBindsMap(pat1, tpt) - registerAsGadtSyms(typeBinds) - scrut <:< tpt.tpe && { - addTypeBindings(typeBinds) - reducePattern(caseBindingMap, scrut, pat1) - } - case pat @ Bind(name: TermName, Typed(_, tpt)) if isImplicit => - val typeBinds = getTypeBindsMap(tpt, tpt) - registerAsGadtSyms(typeBinds) - searchImplicit(pat.symbol.asTerm, tpt) && { - addTypeBindings(typeBinds) - true - } - case pat @ Bind(name: TermName, body) => - reducePattern(caseBindingMap, scrut, body) && { - if (name != nme.WILDCARD) newTermBinding(pat.symbol.asTerm, ref(scrut)) - true - } - case Ident(nme.WILDCARD) => - true - case pat: Literal => - scrut.widenTermRefExpr =:= pat.tpe - case pat: RefTree => - scrut =:= pat.tpe || - scrut.classSymbol.is(Module) && scrut.widen =:= pat.tpe.widen && { - scrut.prefix match { - case _: SingletonType | NoPrefix => true - case _ => false - } - } - case UnApply(unapp, _, pats) => - unapp.tpe.widen match { - case mt: MethodType if mt.paramInfos.length == 1 => - - def reduceSubPatterns(pats: List[Tree], selectors: List[Tree]): Boolean = (pats, selectors) match { - case (Nil, Nil) => true - case (pat :: pats1, selector :: selectors1) => - val elem = newSym(InlineBinderName.fresh(), Synthetic, selector.tpe.widenInlineScrutinee).asTerm - val rhs = constToLiteral(selector) - elem.defTree = rhs - caseBindingMap += ((NoSymbol, ValDef(elem, rhs).withSpan(elem.span))) - reducePattern(caseBindingMap, elem.termRef, pat) && - reduceSubPatterns(pats1, selectors1) - case _ => false - } - - val paramType = mt.paramInfos.head - val paramCls = paramType.classSymbol - if (paramCls.is(Case) && unapp.symbol.is(Synthetic) && scrut <:< paramType) { - val caseAccessors = - if (paramCls.is(Scala2x)) paramCls.caseAccessors.filter(_.is(Method)) - else paramCls.asClass.paramAccessors - val selectors = - for (accessor <- caseAccessors) - yield constToLiteral(reduceProjection(ref(scrut).select(accessor).ensureApplied)) - caseAccessors.length == pats.length && reduceSubPatterns(pats, selectors) - } - else false - case _ => - false - } - case Alternative(pats) => - pats.exists(reducePattern(caseBindingMap, scrut, _)) - case Inlined(EmptyTree, Nil, ipat) => - reducePattern(caseBindingMap, scrut, ipat) - case _ => false - } - } - - /** The initial scrutinee binding: `val $scrutineeN = ` */ - val scrutineeSym = newSym(InlineScrutineeName.fresh(), Synthetic, scrutType).asTerm - val scrutineeBinding = normalizeBinding(ValDef(scrutineeSym, scrutinee)) - - def reduceCase(cdef: CaseDef): MatchReduxWithGuard = { - val caseBindingMap = new mutable.ListBuffer[(Symbol, MemberDef)]() - - def substBindings( - bindings: List[(Symbol, MemberDef)], - bbuf: mutable.ListBuffer[MemberDef], - from: List[Symbol], to: List[Symbol]): (List[MemberDef], List[Symbol], List[Symbol]) = - bindings match { - case (sym, binding) :: rest => - bbuf += binding.subst(from, to).asInstanceOf[MemberDef] - if (sym.exists) substBindings(rest, bbuf, sym :: from, binding.symbol :: to) - else substBindings(rest, bbuf, from, to) - case Nil => (bbuf.toList, from, to) - } - - if (!isImplicit) caseBindingMap += ((NoSymbol, scrutineeBinding)) - val gadtCtx = ctx.fresh.setFreshGADTBounds.addMode(Mode.GadtConstraintInference) - if (reducePattern(caseBindingMap, scrutineeSym.termRef, cdef.pat)(using gadtCtx)) { - val (caseBindings, from, to) = substBindings(caseBindingMap.toList, mutable.ListBuffer(), Nil, Nil) - val (guardOK, canReduceGuard) = - if cdef.guard.isEmpty then (true, true) - else typer.typed(cdef.guard.subst(from, to), defn.BooleanType) match { - case ConstantValue(v: Boolean) => (v, true) - case _ => (false, false) - } - if guardOK then Some((caseBindings.map(_.subst(from, to)), cdef.body.subst(from, to), canReduceGuard)) - else if canReduceGuard then None - else Some((caseBindings.map(_.subst(from, to)), cdef.body.subst(from, to), canReduceGuard)) - } - else None - } - - def recur(cases: List[CaseDef]): MatchRedux = cases match { - case Nil => None - case cdef :: cases1 => - reduceCase(cdef) match - case None => recur(cases1) - case r @ Some((caseBindings, rhs, canReduceGuard)) if canReduceGuard => Some((caseBindings, rhs)) - case _ => None - } - - recur(cases) - } - } - - /** A typer for inlined bodies. Beyond standard typing, an inline typer performs - * the following functions: - * - * 1. Implement constant folding over inlined code - * 2. Selectively expand ifs with constant conditions - * 3. Inline arguments that are by-name closures - * 4. Make sure inlined code is type-correct. - * 5. Make sure that the tree's typing is idempotent (so that future -Ycheck passes succeed) - */ - class InlineTyper(initialErrorCount: Int, @constructorOnly nestingLevel: Int = ctx.nestingLevel + 1) extends ReTyper(nestingLevel) { - import reducer._ - - override def ensureAccessible(tpe: Type, superAccess: Boolean, pos: SrcPos)(using Context): Type = { - tpe match { - case tpe: NamedType if tpe.symbol.exists && !tpe.symbol.isAccessibleFrom(tpe.prefix, superAccess) => - tpe.info match { - case TypeAlias(alias) => return ensureAccessible(alias, superAccess, pos) - case info: ConstantType if tpe.symbol.isStableMember => return info - case _ => - } - case _ => - } - super.ensureAccessible(tpe, superAccess, pos) - } - - /** Enter implicits in scope so that they can be found in implicit search. - * This is important for non-transparent inlines - */ - override def index(trees: List[untpd.Tree])(using Context): Context = - for case tree: untpd.MemberDef <- trees do - if tree.symbol.isOneOf(Flags.GivenOrImplicit) then - ctx.scope.openForMutations.enter(tree.symbol) - ctx - - override def typedIdent(tree: untpd.Ident, pt: Type)(using Context): Tree = - val tree1 = inlineIfNeeded( - tryInlineArg(tree.asInstanceOf[tpd.Tree]) `orElse` super.typedIdent(tree, pt) - ) - tree1 match - case id: Ident if tpd.needsSelect(id.tpe) => - inlining.println(i"expanding $id to selection") - ref(id.tpe.asInstanceOf[TermRef]).withSpan(id.span) - case _ => - tree1 - - override def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = { - val qual1 = typed(tree.qualifier, shallowSelectionProto(tree.name, pt, this)) - val resNoReduce = untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt) - val reducedProjection = reducer.reduceProjection(resNoReduce) - if reducedProjection.isType then - //if the projection leads to a typed tree then we stop reduction - resNoReduce - else - val res = constToLiteral(reducedProjection) - if resNoReduce ne res then - typed(res, pt) // redo typecheck if reduction changed something - else if res.symbol.isInlineMethod then - inlineIfNeeded(res) - else - ensureAccessible(res.tpe, tree.qualifier.isInstanceOf[untpd.Super], tree.srcPos) - res - } - - override def typedIf(tree: untpd.If, pt: Type)(using Context): Tree = - val condCtx = if tree.isInline then ctx.addMode(Mode.ForceInline) else ctx - typed(tree.cond, defn.BooleanType)(using condCtx) match { - case cond1 @ ConstantValue(b: Boolean) => - val selected0 = if (b) tree.thenp else tree.elsep - val selected = if (selected0.isEmpty) tpd.Literal(Constant(())) else typed(selected0, pt) - if (isIdempotentExpr(cond1)) selected - else Block(cond1 :: Nil, selected) - case cond1 => - if (tree.isInline) - errorTree(tree, - em"Cannot reduce `inline if` because its condition is not a constant value: $cond1") - else - cond1.computeNullableDeeply() - val if1 = untpd.cpy.If(tree)(cond = untpd.TypedSplice(cond1)) - super.typedIf(if1, pt) - } - - override def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree = - val vdef1 = - if sym.is(Inline) then - val rhs = typed(vdef.rhs) - sym.info = rhs.tpe - untpd.cpy.ValDef(vdef)(vdef.name, untpd.TypeTree(rhs.tpe), untpd.TypedSplice(rhs)) - else vdef - super.typedValDef(vdef1, sym) - - override def typedApply(tree: untpd.Apply, pt: Type)(using Context): Tree = - def cancelQuotes(tree: Tree): Tree = - tree match - case Quoted(Spliced(inner)) => inner - case _ => tree - val res = cancelQuotes(constToLiteral(betaReduce(super.typedApply(tree, pt)))) match { - case res: Apply if res.symbol == defn.QuotedRuntime_exprSplice - && level == 0 - && !hasInliningErrors => - val expanded = expandMacro(res.args.head, tree.srcPos) - typedExpr(expanded) // Inline calls and constant fold code generated by the macro - case res => - specializeEq(inlineIfNeeded(res)) - } - res - - override def typedTypeApply(tree: untpd.TypeApply, pt: Type)(using Context): Tree = - val tree1 = inlineIfNeeded(constToLiteral(betaReduce(super.typedTypeApply(tree, pt)))) - if tree1.symbol.isQuote then - ctx.compilationUnit.needsStaging = true - tree1 - - override def typedMatch(tree: untpd.Match, pt: Type)(using Context): Tree = - val tree1 = - if tree.isInline then - // TODO this might not be useful if we do not support #11291 - val sel1 = typedExpr(tree.selector)(using ctx.addMode(Mode.ForceInline)) - untpd.cpy.Match(tree)(sel1, tree.cases) - else tree - super.typedMatch(tree1, pt) - - override def typedMatchFinish(tree: untpd.Match, sel: Tree, wideSelType: Type, cases: List[untpd.CaseDef], pt: Type)(using Context) = - if (!tree.isInline || ctx.owner.isInlineMethod) // don't reduce match of nested inline method yet - super.typedMatchFinish(tree, sel, wideSelType, cases, pt) - else { - def selTyped(sel: Tree): Type = sel match { - case Typed(sel2, _) => selTyped(sel2) - case Block(Nil, sel2) => selTyped(sel2) - case Inlined(_, Nil, sel2) => selTyped(sel2) - case _ => sel.tpe - } - val selType = if (sel.isEmpty) wideSelType else selTyped(sel) - reduceInlineMatch(sel, selType, cases.asInstanceOf[List[CaseDef]], this) match { - case Some((caseBindings, rhs0)) => - // drop type ascriptions/casts hiding pattern-bound types (which are now aliases after reducing the match) - // note that any actually necessary casts will be reinserted by the typing pass below - val rhs1 = rhs0 match { - case Block(stats, t) if t.span.isSynthetic => - t match { - case Typed(expr, _) => - Block(stats, expr) - case TypeApply(sel@Select(expr, _), _) if sel.symbol.isTypeCast => - Block(stats, expr) - case _ => - rhs0 - } - case _ => rhs0 - } - val (usedBindings, rhs2) = dropUnusedDefs(caseBindings, rhs1) - val rhs = seq(usedBindings, rhs2) - inlining.println(i"""--- reduce: - |$tree - |--- to: - |$rhs""") - typedExpr(rhs, pt) - case None => - def guardStr(guard: untpd.Tree) = if (guard.isEmpty) "" else i" if $guard" - def patStr(cdef: untpd.CaseDef) = i"case ${cdef.pat}${guardStr(cdef.guard)}" - val msg = - if (tree.selector.isEmpty) - em"""cannot reduce summonFrom with - | patterns : ${tree.cases.map(patStr).mkString("\n ")}""" - else - em"""cannot reduce inline match with - | scrutinee: $sel : ${selType} - | patterns : ${tree.cases.map(patStr).mkString("\n ")}""" - errorTree(tree, msg) - } - } - - override def newLikeThis(nestingLevel: Int): Typer = new InlineTyper(initialErrorCount, nestingLevel) - - /** True if this inline typer has already issued errors */ - override def hasInliningErrors(using Context) = ctx.reporter.errorCount > initialErrorCount - - private def inlineIfNeeded(tree: Tree)(using Context): Tree = - val meth = tree.symbol - if meth.isAllOf(DeferredInline) then - errorTree(tree, i"Deferred inline ${meth.showLocated} cannot be invoked") - else if Inliner.needsInlining(tree) then Inliner.inlineCall(tree) - else tree - - override def typedUnadapted(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree = - super.typedUnadapted(tree, pt, locked) match - case member: MemberDef => member.setDefTree - case tree => tree - } - - def specializeEq(tree: Tree): Tree = - tree match - case Apply(sel @ Select(arg1, opName), arg2 :: Nil) - if sel.symbol == defn.Any_== || sel.symbol == defn.Any_!= => - defn.ScalaValueClasses().find { cls => - arg1.tpe.derivesFrom(cls) && arg2.tpe.derivesFrom(cls) - } match { - case Some(cls) => - val newOp = cls.requiredMethod(opName, List(cls.typeRef)) - arg1.select(newOp).withSpan(sel.span).appliedTo(arg2).withSpan(tree.span) - case None => tree - } - case _ => - tree - - /** Drop any side-effect-free bindings that are unused in expansion or other reachable bindings. - * Inline def bindings that are used only once. - */ - def dropUnusedDefs(bindings: List[MemberDef], tree: Tree)(using Context): (List[MemberDef], Tree) = { - // inlining.println(i"drop unused $bindings%, % in $tree") - val (termBindings, typeBindings) = bindings.partition(_.symbol.isTerm) - if (typeBindings.nonEmpty) { - val typeBindingsSet = typeBindings.foldLeft[SimpleIdentitySet[Symbol]](SimpleIdentitySet.empty)(_ + _.symbol) - val inlineTypeBindings = new TreeTypeMap( - typeMap = new TypeMap() { - override def apply(tp: Type): Type = tp match { - case tr: TypeRef if tr.prefix.eq(NoPrefix) && typeBindingsSet.contains(tr.symbol) => - val TypeAlias(res) = tr.info: @unchecked - res - case tp => mapOver(tp) - } - }, - treeMap = { - case ident: Ident if ident.isType && typeBindingsSet.contains(ident.symbol) => - val TypeAlias(r) = ident.symbol.info: @unchecked - TypeTree(r).withSpan(ident.span) - case tree => tree - } - ) - val Block(termBindings1, tree1) = inlineTypeBindings(Block(termBindings, tree)) - dropUnusedDefs(termBindings1.asInstanceOf[List[ValOrDefDef]], tree1) - } - else { - val refCount = MutableSymbolMap[Int]() - val bindingOfSym = MutableSymbolMap[MemberDef]() - - def isInlineable(binding: MemberDef) = binding match { - case ddef @ DefDef(_, Nil, _, _) => isElideableExpr(ddef.rhs) - case vdef @ ValDef(_, _, _) => isElideableExpr(vdef.rhs) - case _ => false - } - for (binding <- bindings if isInlineable(binding)) { - refCount(binding.symbol) = 0 - bindingOfSym(binding.symbol) = binding - } - - val countRefs = new TreeTraverser { - override def traverse(t: Tree)(using Context) = { - def updateRefCount(sym: Symbol, inc: Int) = - for (x <- refCount.get(sym)) refCount(sym) = x + inc - def updateTermRefCounts(t: Tree) = - t.typeOpt.foreachPart { - case ref: TermRef => updateRefCount(ref.symbol, 2) // can't be inlined, so make sure refCount is at least 2 - case _ => - } - - t match { - case t: RefTree => - updateRefCount(t.symbol, 1) - updateTermRefCounts(t) - case _: New | _: TypeTree => - updateTermRefCounts(t) - case _ => - } - traverseChildren(t) - } - } - countRefs.traverse(tree) - for (binding <- bindings) countRefs.traverse(binding) - - def retain(boundSym: Symbol) = { - refCount.get(boundSym) match { - case Some(x) => x > 1 || x == 1 && !boundSym.is(Method) - case none => true - } - } && !boundSym.is(Inline) - - val inlineBindings = new TreeMap { - override def transform(t: Tree)(using Context) = t match { - case t: RefTree => - val sym = t.symbol - val t1 = refCount.get(sym) match { - case Some(1) => - bindingOfSym(sym) match { - case binding: ValOrDefDef => integrate(binding.rhs, sym) - } - case none => t - } - super.transform(t1) - case t: Apply => - val t1 = super.transform(t) - if (t1 `eq` t) t else reducer.betaReduce(t1) - case Block(Nil, expr) => - super.transform(expr) - case _ => - super.transform(t) - } - } - - val retained = bindings.filterConserve(binding => retain(binding.symbol)) - if (retained `eq` bindings) - (bindings, tree) - else { - val expanded = inlineBindings.transform(tree) - dropUnusedDefs(retained, expanded) - } - } - } - - private def expandMacro(body: Tree, splicePos: SrcPos)(using Context) = { - assert(level == 0) - val inlinedFrom = enclosingInlineds.last - val dependencies = macroDependencies(body) - val suspendable = ctx.compilationUnit.isSuspendable - if dependencies.nonEmpty && !ctx.reporter.errorsReported then - for sym <- dependencies do - if ctx.compilationUnit.source.file == sym.associatedFile then - report.error(em"Cannot call macro $sym defined in the same source file", call.srcPos) - if (suspendable && ctx.settings.XprintSuspension.value) - report.echo(i"suspension triggered by macro call to ${sym.showLocated} in ${sym.associatedFile}", call.srcPos) - if suspendable then - ctx.compilationUnit.suspend() // this throws a SuspendException - - val evaluatedSplice = inContext(quoted.MacroExpansion.context(inlinedFrom)) { - Splicer.splice(body, splicePos, inlinedFrom.srcPos, MacroClassLoader.fromContext) - } - val inlinedNormailizer = new TreeMap { - override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match { - case Inlined(EmptyTree, Nil, expr) if enclosingInlineds.isEmpty => transform(expr) - case _ => super.transform(tree) - } - } - val normalizedSplice = inlinedNormailizer.transform(evaluatedSplice) - if (normalizedSplice.isEmpty) normalizedSplice - else normalizedSplice.withSpan(splicePos.span) - } - - /** Return the set of symbols that are referred at level -1 by the tree and defined in the current run. - * This corresponds to the symbols that will need to be interpreted. - */ - private def macroDependencies(tree: Tree)(using Context) = - new TreeAccumulator[List[Symbol]] { - private var level = -1 - override def apply(syms: List[Symbol], tree: tpd.Tree)(using Context): List[Symbol] = - if (level != -1) foldOver(syms, tree) - else tree match { - case tree: RefTree if level == -1 && tree.symbol.isDefinedInCurrentRun && !tree.symbol.isLocal => - foldOver(tree.symbol :: syms, tree) - case Quoted(body) => - level += 1 - try apply(syms, body) - finally level -= 1 - case Spliced(body) => - level -= 1 - try apply(syms, body) - finally level += 1 - case SplicedType(body) => - level -= 1 - try apply(syms, body) - finally level += 1 - case _ => - foldOver(syms, tree) - } - }.apply(Nil, tree) - - object ConstantValue { - def unapply(tree: Tree)(using Context): Option[Any] = - tree match - case Typed(expr, _) => unapply(expr) - case Inlined(_, Nil, expr) => unapply(expr) - case Block(Nil, expr) => unapply(expr) - case _ => - tree.tpe.widenTermRefExpr.normalized match - case ConstantType(Constant(x)) => Some(x) - case _ => None - } - - -} diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index de5ffdeea986..3a821f1dc65d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -17,6 +17,7 @@ import tpd.tpes import Variances.alwaysInvariant import config.{Config, Feature} import config.Printers.typr +import inlines.{Inlines, PrepareInlineable} import parsing.JavaParsers.JavaParser import parsing.Parsers.Parser import Annotations._ @@ -844,7 +845,7 @@ class Namer { typer: Typer => def rhsToInline(using Context): tpd.Tree = if !original.symbol.exists && !hasDefinedSymbol(original) then throw - if sym.isCompleted then Inliner.MissingInlineInfo() + if sym.isCompleted then Inlines.MissingInlineInfo() else CyclicReference(sym) val mdef = typedAheadExpr(original).asInstanceOf[tpd.DefDef] PrepareInlineable.wrapRHS(original, mdef.tpt, mdef.rhs) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 7889f6fc0ad1..d9c23c408dc4 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -11,6 +11,7 @@ import Constants._ import util.{Stats, SimpleIdentityMap, SimpleIdentitySet} import Decorators._ import Uniques._ +import inlines.Inlines import config.Printers.typr import util.SourceFile import TypeComparer.necessarySubType @@ -109,7 +110,7 @@ object ProtoTypes { * achieved by replacing expected type parameters with wildcards. */ def constrainResult(meth: Symbol, mt: Type, pt: Type)(using Context): Boolean = - if (Inliner.isInlineable(meth)) { + if (Inlines.isInlineable(meth)) { constrainResult(mt, wildApprox(pt)) true } diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index 13733977bbde..fa29f450be2a 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -15,6 +15,7 @@ import dotty.tools.dotc.core.StagingContext._ import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.Types._ +import dotty.tools.dotc.inlines.PrepareInlineable import dotty.tools.dotc.transform.SymUtils._ import dotty.tools.dotc.typer.Implicits._ import dotty.tools.dotc.typer.Inferencing._ diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 265cd3e2c76d..71a8872343b4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -29,6 +29,7 @@ import Inferencing._ import Dynamic.isDynamicExpansion import EtaExpansion.etaExpand import TypeComparer.CompareResult +import inlines.{Inlines, PrepareInlineable} import util.Spans._ import util.common._ import util.{Property, SimpleIdentityMap, SrcPos} @@ -1698,7 +1699,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if (bounds != null) sym.info = bounds } b - case t: UnApply if t.symbol.is(Inline) => Inliner.inlinedUnapply(t) + case t: UnApply if t.symbol.is(Inline) => Inlines.inlinedUnapply(t) case t => t } } @@ -3032,7 +3033,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer else ctx.withNotNullInfos(initialNotNullInfos) typed(mdef)(using newCtx) match { case mdef1: DefDef - if mdef1.symbol.is(Inline, butNot = Deferred) && !Inliner.bodyToInline(mdef1.symbol).isEmpty => + if mdef1.symbol.is(Inline, butNot = Deferred) && !Inlines.bodyToInline(mdef1.symbol).isEmpty => buf ++= inlineExpansion(mdef1) // replace body with expansion, because it will be used as inlined body // from separately compiled files - the original BodyAnnotation is not kept. @@ -3120,8 +3121,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer * Overwritten in Retyper to return `mdef` unchanged. */ protected def inlineExpansion(mdef: DefDef)(using Context): List[Tree] = - tpd.cpy.DefDef(mdef)(rhs = Inliner.bodyToInline(mdef.symbol)) - :: (if mdef.symbol.isRetainedInlineMethod then Inliner.bodyRetainer(mdef) :: Nil else Nil) + tpd.cpy.DefDef(mdef)(rhs = Inlines.bodyToInline(mdef.symbol)) + :: (if mdef.symbol.isRetainedInlineMethod then Inlines.bodyRetainer(mdef) :: Nil else Nil) def typedExpr(tree: untpd.Tree, pt: Type = WildcardType)(using Context): Tree = withoutMode(Mode.PatternOrTypeBits)(typed(tree, pt)) @@ -3719,12 +3720,12 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } else val meth = methPart(tree).symbol - if meth.isAllOf(DeferredInline) && !Inliner.inInlineMethod then + if meth.isAllOf(DeferredInline) && !Inlines.inInlineMethod then errorTree(tree, i"Deferred inline ${meth.showLocated} cannot be invoked") - else if Inliner.needsInlining(tree) then + else if Inlines.needsInlining(tree) then tree.tpe <:< wildApprox(pt) val errorCount = ctx.reporter.errorCount - val inlined = Inliner.inlineCall(tree) + val inlined = Inlines.inlineCall(tree) if ((inlined ne tree) && errorCount == ctx.reporter.errorCount) readaptSimplified(inlined) else inlined else if (tree.symbol.isScala2Macro && diff --git a/compiler/src/dotty/tools/dotc/typer/TyperPhase.scala b/compiler/src/dotty/tools/dotc/typer/TyperPhase.scala index 71e1dc961c00..44eed484b823 100644 --- a/compiler/src/dotty/tools/dotc/typer/TyperPhase.scala +++ b/compiler/src/dotty/tools/dotc/typer/TyperPhase.scala @@ -9,6 +9,7 @@ import Symbols._ import ImportInfo.withRootImports import parsing.{Parser => ParserPhase} import config.Printers.typr +import inlines.PrepareInlineable import util.Stats._ /**