@@ -424,7 +424,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
424424 computeParamBindings(tp.resultType, Nil , argss)
425425 case tp : MethodType =>
426426 if argss.isEmpty then
427- ctx.error(i " mising arguments for inline method $inlinedMethod" , call.sourcePos)
427+ ctx.error(i " missing arguments for inline method $inlinedMethod" , call.sourcePos)
428428 false
429429 else
430430 tp.paramNames.lazyZip(tp.paramInfos).lazyZip(argss.head).foreach { (name, paramtp, arg) =>
@@ -477,6 +477,73 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
477477 tpe.cls.isContainedIn(inlinedMethod) ||
478478 tpe.cls.is(Package )
479479
480+ /** Very similar to TreeInfo.isPureExpr, but with the following inliner-only exceptions:
481+ * - synthetic case class apply methods, when the case class constructor is empty, are
482+ * elideable but not pure. Elsewhere, accessing the apply method might cause the initialization
483+ * of a containing object so they are merely idempotent.
484+ */
485+ object isElideableExpr {
486+ def isStatElideable (tree : Tree )(implicit ctx : Context ): Boolean = unsplice(tree) match {
487+ case EmptyTree
488+ | TypeDef (_, _)
489+ | Import (_, _)
490+ | DefDef (_, _, _, _, _) =>
491+ true
492+ case vdef @ ValDef (_, _, _) =>
493+ if (vdef.symbol.flags is Mutable ) false else apply(vdef.rhs)
494+ case _ =>
495+ false
496+ }
497+
498+ def apply (tree : Tree ): Boolean = unsplice(tree) match {
499+ case EmptyTree
500+ | This (_)
501+ | Super (_, _)
502+ | Literal (_) =>
503+ true
504+ case Ident (_) =>
505+ isPureRef(tree)
506+ case Select (qual, _) =>
507+ if (tree.symbol.is(Erased )) true
508+ else isPureRef(tree) && apply(qual)
509+ case New (_) | Closure (_, _, _) =>
510+ true
511+ case TypeApply (fn, _) =>
512+ if (fn.symbol.is(Erased ) || fn.symbol == defn.InternalQuoted_typeQuote ) true else apply(fn)
513+ case Apply (fn, args) =>
514+ def isKnownPureOp (sym : Symbol ) =
515+ sym.owner.isPrimitiveValueClass
516+ || sym.owner == defn.StringClass
517+ || defn.pureMethods.contains(sym)
518+ val isCaseClassApply = {
519+ val cls = tree.tpe.classSymbol
520+ val meth = fn.symbol
521+ meth.name == nme.apply &&
522+ meth.flags.is(Synthetic ) &&
523+ meth.owner.linkedClass.is(Case ) &&
524+ cls.isNoInitsClass
525+ }
526+ if (tree.tpe.isInstanceOf [ConstantType ] && isKnownPureOp(tree.symbol) // A constant expression with pure arguments is pure.
527+ || (fn.symbol.isStableMember && ! fn.symbol.is(Lazy ))
528+ || fn.symbol.isPrimaryConstructor && fn.symbol.owner.isNoInitsClass) // TODO: include in isStable?
529+ apply(fn) && args.forall(apply)
530+ else if (isCaseClassApply)
531+ args.forall(apply)
532+ else if (fn.symbol.is(Erased )) true
533+ else false
534+ case Typed (expr, _) =>
535+ apply(expr)
536+ case Block (stats, expr) =>
537+ apply(expr) && stats.forall(isStatElideable)
538+ case Inlined (_, bindings, expr) =>
539+ apply(expr) && bindings.forall(isStatElideable)
540+ case NamedArg (_, expr) =>
541+ apply(expr)
542+ case _ =>
543+ false
544+ }
545+ }
546+
480547 /** Populate `thisProxy` and `paramProxy` as follows:
481548 *
482549 * 1a. If given type refers to a static this, thisProxy binds it to corresponding global reference,
@@ -739,13 +806,16 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
739806 Some (meth.owner.linkedClass, args, Nil , false )
740807 else None
741808 }
809+ case Typed (inner, _) =>
810+ // drop the ascribed tpt. We only need it if we can't find a NewInstance
811+ unapply(inner)
742812 case Ident (_) =>
743813 val binding = tree.symbol.defTree
744814 for ((cls, reduced, prefix, precomputed) <- unapply(binding))
745815 yield (cls, reduced, prefix, precomputed || binding.isInstanceOf [ValDef ])
746816 case Inlined (_, bindings, expansion) =>
747817 unapplyLet(bindings, expansion)
748- case Block (stats, expr) if isPureExpr (tree) =>
818+ case Block (stats, expr) if isElideableExpr (tree) =>
749819 unapplyLet(stats, expr)
750820 case _ =>
751821 None
@@ -778,13 +848,13 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
778848 .reporting(i " projecting $tree -> $result" , inlining)
779849 val arg = args(idx)
780850 if (precomputed)
781- if (isPureExpr (arg)) finish(arg)
851+ if (isElideableExpr (arg)) finish(arg)
782852 else tree // nothing we can do here, projection would duplicate side effect
783853 else {
784854 // newInstance is evaluated in place, need to reflect side effects of
785855 // arguments in the order they were written originally
786856 def collectImpure (from : Int , end : Int ) =
787- (from until end).filterNot(i => isPureExpr (args(i))).toList.map(args)
857+ (from until end).filterNot(i => isElideableExpr (args(i))).toList.map(args)
788858 val leading = collectImpure(0 , idx)
789859 val trailing = collectImpure(idx + 1 , args.length)
790860 val argInPlace =
@@ -1041,7 +1111,9 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
10411111 case (Nil , Nil ) => true
10421112 case (pat :: pats1, selector :: selectors1) =>
10431113 val elem = newSym(InlineBinderName .fresh(), Synthetic , selector.tpe.widenTermRefExpr).asTerm
1044- caseBindingMap += ((NoSymbol , ValDef (elem, constToLiteral(selector)).withSpan(elem.span)))
1114+ val rhs = constToLiteral(selector)
1115+ elem.defTree = rhs
1116+ caseBindingMap += ((NoSymbol , ValDef (elem, rhs).withSpan(elem.span)))
10451117 reducePattern(caseBindingMap, elem.termRef, pat) &&
10461118 reduceSubPatterns(pats1, selectors1)
10471119 case _ => false
@@ -1143,9 +1215,14 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
11431215 override def typedSelect (tree : untpd.Select , pt : Type )(using Context ): Tree = {
11441216 assert(tree.hasType, tree)
11451217 val qual1 = typed(tree.qualifier, selectionProto(tree.name, pt, this ))
1146- val res = constToLiteral(untpd.cpy.Select (tree)(qual1, tree.name).withType(tree.typeOpt))
1147- ensureAccessible(res.tpe, tree.qualifier.isInstanceOf [untpd.Super ], tree.sourcePos)
1148- checkStaging(res)
1218+ val resNoReduce = untpd.cpy.Select (tree)(qual1, tree.name).withType(tree.typeOpt)
1219+ val resMaybeReduced = constToLiteral(reducer.reduceProjection(resNoReduce))
1220+ if (resNoReduce ne resMaybeReduced)
1221+ typed(resMaybeReduced, pt) // redo typecheck if reduction changed something
1222+ else
1223+ val res = resMaybeReduced
1224+ ensureAccessible(res.tpe, tree.qualifier.isInstanceOf [untpd.Super ], tree.sourcePos)
1225+ checkStaging(res)
11491226 }
11501227
11511228 private def checkStaging (tree : Tree ): tree.type =
@@ -1264,8 +1341,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
12641341 val bindingOfSym = newMutableSymbolMap[MemberDef ]
12651342
12661343 def isInlineable (binding : MemberDef ) = binding match {
1267- case ddef @ DefDef (_, Nil , Nil , _, _) => isPureExpr (ddef.rhs)
1268- case vdef @ ValDef (_, _, _) => isPureExpr (vdef.rhs)
1344+ case ddef @ DefDef (_, Nil , Nil , _, _) => isElideableExpr (ddef.rhs)
1345+ case vdef @ ValDef (_, _, _) => isElideableExpr (vdef.rhs)
12691346 case _ => false
12701347 }
12711348 for (binding <- bindings if isInlineable(binding)) {
0 commit comments