@@ -424,7 +424,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
424
424
computeParamBindings(tp.resultType, Nil , argss)
425
425
case tp : MethodType =>
426
426
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)
428
428
false
429
429
else
430
430
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) {
477
477
tpe.cls.isContainedIn(inlinedMethod) ||
478
478
tpe.cls.is(Package )
479
479
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
+
480
547
/** Populate `thisProxy` and `paramProxy` as follows:
481
548
*
482
549
* 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) {
739
806
Some (meth.owner.linkedClass, args, Nil , false )
740
807
else None
741
808
}
809
+ case Typed (inner, _) =>
810
+ // drop the ascribed tpt. We only need it if we can't find a NewInstance
811
+ unapply(inner)
742
812
case Ident (_) =>
743
813
val binding = tree.symbol.defTree
744
814
for ((cls, reduced, prefix, precomputed) <- unapply(binding))
745
815
yield (cls, reduced, prefix, precomputed || binding.isInstanceOf [ValDef ])
746
816
case Inlined (_, bindings, expansion) =>
747
817
unapplyLet(bindings, expansion)
748
- case Block (stats, expr) if isPureExpr (tree) =>
818
+ case Block (stats, expr) if isElideableExpr (tree) =>
749
819
unapplyLet(stats, expr)
750
820
case _ =>
751
821
None
@@ -778,13 +848,13 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
778
848
.reporting(i " projecting $tree -> $result" , inlining)
779
849
val arg = args(idx)
780
850
if (precomputed)
781
- if (isPureExpr (arg)) finish(arg)
851
+ if (isElideableExpr (arg)) finish(arg)
782
852
else tree // nothing we can do here, projection would duplicate side effect
783
853
else {
784
854
// newInstance is evaluated in place, need to reflect side effects of
785
855
// arguments in the order they were written originally
786
856
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)
788
858
val leading = collectImpure(0 , idx)
789
859
val trailing = collectImpure(idx + 1 , args.length)
790
860
val argInPlace =
@@ -1041,7 +1111,9 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
1041
1111
case (Nil , Nil ) => true
1042
1112
case (pat :: pats1, selector :: selectors1) =>
1043
1113
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)))
1045
1117
reducePattern(caseBindingMap, elem.termRef, pat) &&
1046
1118
reduceSubPatterns(pats1, selectors1)
1047
1119
case _ => false
@@ -1143,9 +1215,14 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
1143
1215
override def typedSelect (tree : untpd.Select , pt : Type )(using Context ): Tree = {
1144
1216
assert(tree.hasType, tree)
1145
1217
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)
1149
1226
}
1150
1227
1151
1228
private def checkStaging (tree : Tree ): tree.type =
@@ -1264,8 +1341,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
1264
1341
val bindingOfSym = newMutableSymbolMap[MemberDef ]
1265
1342
1266
1343
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)
1269
1346
case _ => false
1270
1347
}
1271
1348
for (binding <- bindings if isInlineable(binding)) {
0 commit comments