Skip to content

Commit b3f43df

Browse files
committed
Optimize creation and pattern matching with Ok and Err
1 parent 03af750 commit b3f43df

5 files changed

Lines changed: 167 additions & 59 deletions

File tree

compiler/src/dotty/tools/dotc/inlines/Inliner.scala

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import config.Printers.inlining
1515
import ErrorReporting.errorTree
1616
import util.{SimpleIdentitySet, SrcPos}
1717
import Nullables.computeNullableDeeply
18+
import config.Printers.transforms
1819

1920
import collection.mutable
2021
import reporting.trace
@@ -211,6 +212,41 @@ object Inliner:
211212
else
212213
constToLiteral(rootTree)
213214

215+
/** Can a value of type `tp` be the unit value `()` at runtime? That's the case if
216+
* `tp` is not a class type, since then it could still be instantiated to `Unit`,
217+
* or if it is one of the classes that `()` is an instance of. Note that `()` is
218+
* represented as a `BoxedUnit` where a reference type is expected, so `Object`
219+
* and `java.io.Serializable` have to be counted in as well.
220+
*/
221+
private def canBeUnit(tp: Type)(using Context): Boolean =
222+
val cls = tp.widenDealias.typeSymbol
223+
!cls.isClass
224+
|| defn.UnitClass.derivesFrom(cls)
225+
|| cls == defn.ObjectClass
226+
|| cls == defn.JavaSerializableClass
227+
228+
/** If tree is an equality test `==` with known outcome and no side effects, replace it
229+
* by a constant true or false.
230+
* Known outcome means currently: one argument is of Unit type and the other one's
231+
* type tells us whether it can be the unit value or not.
232+
*/
233+
def reduceUnitEQ(tree: Tree)(using Context): Tree = tree match
234+
case Apply(sel @ Select(arg1, nme.EQ), arg2 :: Nil) if isPureExpr(arg1) && isPureExpr(arg2) =>
235+
def const(b: Boolean) =
236+
cpy.Literal(tree)(Constant(b))
237+
.showing(i"REDUCE $tree to $result in ${ctx.compilationUnit} in ${ctx.owner.ownersIterator.toList}/${arg1.tpe},${arg2.tpe}", transforms)
238+
def reduceUnit(tp1: Type, tp2: Type) =
239+
if tp1.isRef(defn.UnitClass) then
240+
if tp2.isRef(defn.UnitClass) then const(true)
241+
else if !canBeUnit(tp2) then const(false)
242+
else EmptyTree
243+
else EmptyTree
244+
val tp1 = arg1.tpe.widen
245+
val tp2 = arg2.tpe.widen
246+
reduceUnit(tp1, tp2).orElse(reduceUnit(tp2, tp1)).orElse(tree)
247+
case _ =>
248+
tree
249+
214250
private[inlines] def newSym(name: Name, flags: FlagSet, info: Type, span: Span)(using Context): Symbol =
215251
newSymbol(ctx.owner, name, flags, info, coord = span)
216252
end Inliner
@@ -800,7 +836,7 @@ class Inliner(val call: tpd.Tree)(using Context):
800836
// corresponding arguments or proxies on the type and term level. It also changes
801837
// the owner from the inlined method to the current owner.
802838

803-
// This is reused through InlineTraitAncestors for inline traits, so inlinedMethod might not exist there
839+
// This is reused through InlineTraitAncestors for inline traits, so inlinedMethod might not exist there
804840
val oldOwners = if (inlinedMethod.exists) then inlinedMethod :: Nil else Nil
805841
val newOwners = if (inlinedMethod.exists) then ctx.owner :: Nil else Nil
806842

compiler/src/dotty/tools/dotc/inlines/Inlines.scala

Lines changed: 45 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -129,15 +129,15 @@ object Inlines:
129129
private def inlineTraitAncestors(cls: TypeDef)(using Context): List[Tree] = cls match {
130130
case tpd.TypeDef(_, tmpl: Template) =>
131131
val parentTrees: Map[Symbol, Tree] = tmpl.parents.map(par => symbolFromParent(par) -> par).toMap.filter(_._1.isInlineTrait)
132-
132+
133133
// TODO: We need to stop inlining if there is a non-inline trait or class that sits between the inline trait and the current class.
134-
// Because we also inline into other inline traits, it should be possible to do this by just
134+
// Because we also inline into other inline traits, it should be possible to do this by just
135135
// looking at the direct parents of the class instead of also needing to look at the indirect parents (baseClasses).
136136
// See inline-trait-non-inline-blocks-inlining.scala
137137
val ancestors: List[ClassSymbol] =
138138
cls.tpe.baseClasses.filter(sym => sym != cls.symbol && sym.isInlineTrait
139139
&& !(cls.symbol.asClass.ownersIterator.toList.tail.exists(p => p.isInlineTrait)) // We can skip anything that would be inlined into a class that lives somewhere inside an inline trait
140-
// because it must be on the RHS of a member definition in the inline trait and so pruned out later
140+
// because it must be on the RHS of a member definition in the inline trait and so pruned out later
141141
)
142142

143143
ancestors.flatMap(ancestor =>
@@ -151,7 +151,7 @@ object Inlines:
151151
report.error(s"unknown base type ${baseTpe.show} for ancestor ${ancestor.show} of ${cls.symbol.show}")
152152
None
153153
parentTrees.get(ancestor).orElse(baseTree.map(_.withSpan(cls.span)))
154-
).flatMap { tree =>
154+
).flatMap { tree =>
155155
tree.tpe match {
156156
case Specialization(spec) if spec.hasSpecializedParams && !spec.isFullySpecialized => None // these can only exist in cases where we don't want to inline because:
157157
// 1) they will be pruned out later anyway and if we inline them we will create a loop (as in tests/pos/specialized-trait-inlining-causes-implementation-required-loop-bad.scala)
@@ -286,7 +286,7 @@ object Inlines:
286286
tree3
287287
end inlineCall
288288

289-
private def updateFlagsFromInlinedParent(child: FlagSet, parent: FlagSet): FlagSet =
289+
private def updateFlagsFromInlinedParent(child: FlagSet, parent: FlagSet): FlagSet =
290290
var updatedFlags = child
291291
// Parent needs to be initialised so child must also as initialisers have been inlined
292292
if (!parent.is(NoInits))
@@ -297,8 +297,8 @@ object Inlines:
297297
updatedFlags &~= PureInterface
298298
updatedFlags
299299

300-
private def checkInnerClasses(tmpl: Template)(using Context) =
301-
tmpl.body.foreach {
300+
private def checkInnerClasses(tmpl: Template)(using Context) =
301+
tmpl.body.foreach {
302302
// If we want to add these back, some work was done on this in the original Master Thesis
303303
// (https://infoscience.epfl.ch/server/api/core/bitstreams/9413f583-46bc-4106-b994-0be32f20eeba/content)
304304
case innerClass: TypeDef if innerClass.symbol.isClass => report.error("Inline traits may not define inner classes or traits.", innerClass.srcPos)
@@ -320,13 +320,13 @@ object Inlines:
320320
end checkAndTransformInlineTrait
321321

322322

323-
private def checkInlineTraitOverrides(clsSym: ClassSymbol)(using Context) =
324-
// We need to enforce `override` modifier constraints to ensure that the behaviour is the same as ordinary traits.
323+
private def checkInlineTraitOverrides(clsSym: ClassSymbol)(using Context) =
324+
// We need to enforce `override` modifier constraints to ensure that the behaviour is the same as ordinary traits.
325325
// The usual checks only apply in refChecks which is too late for us.
326326
def checkInlineTraitOverride(member: Symbol, other: Symbol) =
327327
if !member.is(Override) && !other.is(Deferred) && member.owner == clsSym then
328328
report.error(
329-
OverrideError("needs `override` modifier",
329+
OverrideError("needs `override` modifier",
330330
other.info,
331331
member,
332332
other,
@@ -336,11 +336,11 @@ object Inlines:
336336
)
337337
else if member.owner != clsSym && other.owner != clsSym
338338
&& !other.owner.derivesFrom(member.owner)
339-
&& !(member.isAnyOverride || member.hasAnnotation(defn.UncheckedOverrideAnnot))
340-
&& (!other.is(Deferred) || other.isAllOf(Given | HasDefault))
341-
&& !member.is(Deferred)
339+
&& !(member.isAnyOverride || member.hasAnnotation(defn.UncheckedOverrideAnnot))
340+
&& (!other.is(Deferred) || other.isAllOf(Given | HasDefault))
341+
&& !member.is(Deferred)
342342
&& !other.name.is(DefaultGetterName) then
343-
343+
344344
report.error(
345345
OverrideError(
346346
s"${clsSym} inherits conflicting members:\n "
@@ -355,7 +355,7 @@ object Inlines:
355355
,
356356
clsSym.srcPos
357357
)
358-
OverridingPairsChecker(clsSym, clsSym.thisType).checkAll(checkInlineTraitOverride)
358+
OverridingPairsChecker(clsSym, clsSym.thisType).checkAll(checkInlineTraitOverride)
359359

360360
def inlineParentInlineTraits(cls: Tree)(using Context): Tree =
361361
cls match {
@@ -367,31 +367,31 @@ object Inlines:
367367
if cls.symbol.isAnonymousClass && ancestors.exists(tree => Specialization.unapply(tree.tpe).exists(anc => anc.isSpecialized || anc.isFullySpecializedToTopClassesOrNothing)) then
368368
// No need to inline into specialized trait anonymous class instances; these will later be replaced by $impl$ classes.
369369
return cls
370-
370+
371371
val cycleFound = ancestors.exists { parent =>
372372
val parentSym = symbolFromParent(parent)
373-
val errorPos =
373+
val errorPos =
374374
// Trying to inline into the tree which defines parentSym (need to catch this separately
375-
// as need to catch it before we inline the second time to avoid tripping an assertion)
375+
// as need to catch it before we inline the second time to avoid tripping an assertion)
376376
if cls.symbol.ownersIterator.contains(parentSym) then
377-
Some(cls.srcPos)
378-
else if ctx.inlineTraitState.inlineOrigins(cls.symbol).contains(parentSym) then
377+
Some(cls.srcPos)
378+
else if ctx.inlineTraitState.inlineOrigins(cls.symbol).contains(parentSym) then
379379
// Select the user code that caused this error so we get two errors if there are two problematic inlines, not one
380-
val userPos = tpd.enclosingInlineds.last.srcPos
380+
val userPos = tpd.enclosingInlineds.last.srcPos
381381
// Trying to inline into the inlined body of parentSym not in the defn tree
382-
Some(userPos)
382+
Some(userPos)
383383
else None // Fine
384-
384+
385385
errorPos.foreach(pos =>
386386
report.error(s"Inlining of inline traits looped. Tried to inline ${parentSym} into its own body.", pos)
387387
)
388-
388+
389389
errorPos.nonEmpty
390390
}
391391

392-
if cycleFound then
392+
if cycleFound then
393393
return cls
394-
394+
395395
val newDefs = inContext(ctx.withOwner(cls.symbol)) {
396396
ancestors.foldLeft((List.empty[Tree], impl.body)) {
397397
case ((inlineDefs, childDefs), parent) =>
@@ -400,22 +400,22 @@ object Inlines:
400400
val overriddenSymbols = clsOverriddenSyms ++ inlineDefs.flatMap(_.symbol.allOverriddenSymbols)
401401
// Need to put the new defs first because we process in linearization order to make overridees correct,
402402
// but we want parent definitions to come first so that if child inline traits refer to values defined in a parent
403-
// inline trait these are defined.
404-
val inlinedDefs1 = parentTraitInliner.expandDefs(overriddenSymbols) ::: inlineDefs
403+
// inline trait these are defined.
404+
val inlinedDefs1 = parentTraitInliner.expandDefs(overriddenSymbols) ::: inlineDefs
405405
cls.symbol.flags = updateFlagsFromInlinedParent(cls.symbol.flags, parent.symbol.flags)
406-
406+
407407
val childDefs1 = parentTraitInliner.adaptSuperCalls(childDefs)
408408
(parentTraitInliner.adaptSuperCalls(inlinedDefs1), childDefs1)
409409
}
410410
}
411411

412412
val newbody = newDefs._1 ::: newDefs._2
413413
val paramAccessors = newbody.filter(_.symbol.is(ParamAccessor))
414-
414+
415415
for pacc <- paramAccessors
416416
otherstat <- newbody if !otherstat.symbol.is(ParamAccessor) && otherstat.denot.matches(pacc.denot.asSingleDenotation)
417-
do report.error(s"Inlining of inline trait created name conflict on ${pacc.denot.name}. Constructor parameters of inline receivers may not collide with members of inline traits.", pacc.srcPos)
418-
417+
do report.error(s"Inlining of inline trait created name conflict on ${pacc.denot.name}. Constructor parameters of inline receivers may not collide with members of inline traits.", pacc.srcPos)
418+
419419
val impl1 = cpy.Template(impl)(body = newbody)
420420

421421
cpy.TypeDef(cls)(rhs = impl1)
@@ -721,7 +721,7 @@ object Inlines:
721721
/** The Inlined node representing the inlined call */
722722
def expand(rhsToInline: Tree): Tree =
723723

724-
// Special handling of `requireConst` and `codeOf`
724+
// Special handling of `requireConst`, `codeOf`, and `Ok`
725725
callValueArgss match
726726
case (arg :: Nil) :: Nil =>
727727
if inlinedMethod == defn.Compiletime_requireConst then
@@ -731,6 +731,8 @@ object Inlines:
731731
return unitLiteral.withSpan(call.span)
732732
else if inlinedMethod == defn.Compiletime_codeOf then
733733
return Intrinsics.codeOf(arg, call.srcPos)
734+
else if inlinedMethod == defn.Ok_unapply && arg.tpe.isNotNullNorMaybe then
735+
return arg
734736
case _ =>
735737

736738
// Special handling of `constValue[T]`, `constValueOpt[T]`, `constValueTuple[T]`, `summonInline[T]` and `summonAll[T]`
@@ -919,11 +921,11 @@ object Inlines:
919921
}
920922
end expandDefs
921923

922-
def adaptSuperCalls(defs: List[Tree]) =
924+
def adaptSuperCalls(defs: List[Tree]) =
923925
val ttmap = TreeTypeMap(treeMap = {
924926
// We go through all ancestor inline traits so eventually we will find the one with matching parentSym
925927
case sel@Select(Super(qual, mix), name) if sel.symbol.owner == parentSym =>
926-
// At that point either the method is overridden so needs mangling (and we just copied and mangled it in this inlining phase),
928+
// At that point either the method is overridden so needs mangling (and we just copied and mangled it in this inlining phase),
927929
// or not, in which case call directly by original name. In both cases we are calling the method resulting from inlining, on the
928930
// inline receiver class.
929931
Select(This(ctx.owner.asClass), paramAccessorsMapper.getParamAccessorName(sel.symbol.owner, name).getOrElse(name))
@@ -979,11 +981,11 @@ object Inlines:
979981
}
980982

981983
override protected val inlinerTypeMap: InlinerTypeMap = InlineTraitTypeMap()
982-
984+
983985
override protected val inlinerTreeMap: InlinerTreeMap = InlineTraitTreeMap()
984986

985987
override protected def computeThisBindings(): Unit = ()
986-
988+
987989
override protected def canElideThis(tpe: ThisType): Boolean = true
988990

989991
override protected def inlineCtx(inlineTyper: InlineTyper)(using Context): Context =
@@ -1026,7 +1028,7 @@ object Inlines:
10261028
paramAccessorsMapper
10271029
.getParamAccessorRhs(vdef.symbol.owner, vdef.symbol.name)
10281030
.getOrElse(inlinedRhs(vdef, inlinedSym))
1029-
1031+
10301032
val rhs1 = rhs.changeNonLocalOwners(inlinedSym)
10311033

10321034
tpd.ValDef(inlinedSym.asTerm, rhs1).withSpan(parent.span)
@@ -1051,7 +1053,7 @@ object Inlines:
10511053
ctx.typeAssigner.assignType(untpd.TypeDef(inlinedSym.name.asTypeName, TypeTree(inlinedRhsType)), inlinedSym).withSpan(parent.span)
10521054
else
10531055
tpd.TypeDef(inlinedSym.asType).withSpan(parent.span)
1054-
1056+
10551057

10561058
private def inlinedRhs(vddef: ValOrDefDef, inlinedSym: Symbol)(using Context): Tree =
10571059
val rhs = vddef.rhs.changeOwner(vddef.symbol, inlinedSym)
@@ -1062,14 +1064,14 @@ object Inlines:
10621064
rhs
10631065
else
10641066
val symbolMap = mutable.Map[Symbol, Symbol]()
1065-
// TODO: This inlines also some calls to inline defs that were made in the inline trait body, is that ok?
1067+
// TODO: This inlines also some calls to inline defs that were made in the inline trait body, is that ok?
10661068
val rhs1 = Inlined(tpd.ref(parentSym).withSpan(parent.span), Nil, inlined(rhs)._2.withSpan(parent.span).cloneIn(parentSym.source)).withSpan(parent.span)
1067-
1069+
10681070
// In case of nested inline trait inlines, because BodyAnnotation is out of date,
10691071
// body inlined misses nested expansion, but we have the symbols for the items that should be there
10701072
// Remove them so that they can be inlined properly later.
10711073
val ttmap = TreeTypeMap(treeMap = {
1072-
case tree@TypeDef(name, tmpl: Template) if Inlines.needsInlining(tree) =>
1074+
case tree@TypeDef(name, tmpl: Template) if Inlines.needsInlining(tree) =>
10731075
val newSym = tree.symbol.copy(coord = spanCoord(tree.span)) // Coord should correspond to original location because we will inline from there.
10741076
newSym.info = ClassInfo(tree.symbol.owner.thisType, newSym.asClass, tree.symbol.asClass.parentTypes, Scopes.newScope)
10751077

@@ -1155,7 +1157,7 @@ object Inlines:
11551157

11561158
class InlineTraitState(
11571159
// For a class symbol created during inlining of an inline trait,
1158-
// the chain of inlined traits which produced it. We don't actually care about the order.
1160+
// the chain of inlined traits which produced it. We don't actually care about the order.
11591161
// Used as a "seen list" for cycle checking. Persists across invocations of InlineParentTrait
11601162
val inlineOrigins: mutable.Map[Symbol, Set[Symbol]] = mutable.HashMap[Symbol, Set[Symbol]]().withDefaultValue(Set.empty),
11611163
val inlineTraitsPhase: InlineTraitState.InlineContext = InlineTraitState.InlineContext.None

compiler/src/dotty/tools/dotc/transform/BetaReduce.scala

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ class BetaReduce extends MiniPhase:
4545
if app1 ne app then report.log(i"beta reduce $app -> $app1")
4646
app1
4747

48+
/** Cleanup ifs after reduceUnitEQ */
49+
override def transformIf(tree: If)(using Context): Tree = tree.cond match
50+
case Literal(Constant(true)) => tree.thenp
51+
case Literal(Constant(false)) => tree.elsep
52+
case _ => tree
53+
4854
object BetaReduce:
4955
import ast.tpd.*
5056

@@ -71,6 +77,9 @@ object BetaReduce:
7177
* type X1 = T1; ...; type Xm = Tm;val/def x1 = e1; ...; val/def xn = en; b
7278
*
7379
* This beta-reduction preserves the integrity of `Inlined` tree nodes.
80+
*
81+
* Also, replace some == tests between constants with known outcomes by true/false.
82+
* This is useful since such tests can arise though inlining, e.g. in maybe-translation.scala.
7483
*/
7584
def apply(tree: Tree)(using Context): Tree =
7685
val bindingsBuf = new ListBuffer[DefTree]
@@ -111,7 +120,7 @@ object BetaReduce:
111120
case None =>
112121
tree
113122
case _ =>
114-
tree
123+
inlines.Inliner.reduceUnitEQ(tree)
115124

116125
/** Beta-reduces a call to `ddef` with arguments `args` and registers new bindings.
117126
* @return optionally, the expanded call, or none if the actual argument

0 commit comments

Comments
 (0)