Skip to content

Commit 21cb490

Browse files
authored
Merge pull request #6491 from dotty-staging/implicit-alias-rhs
Cache Right Hand Side of Parameterless Alias Implicits
2 parents 4693a88 + 659e1f1 commit 21cb490

37 files changed

+435
-186
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class Compiler {
6262
new ExpandSAMs, // Expand single abstract method closures to anonymous classes
6363
new ProtectedAccessors, // Add accessors for protected members
6464
new ExtensionMethods, // Expand methods of value classes with extension methods
65+
new CacheAliasImplicits, // Cache RHS of parameterless alias implicits
6566
new ShortcutImplicits, // Allow implicit functions without creating closures
6667
new ByNameClosures, // Expand arguments to by-name parameters to closures
6768
new HoistSuperArgs, // Hoist complex arguments of supercalls to enclosing scope

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,15 @@ object desugar {
512512
case _ =>
513513
constrVparamss
514514
}
515-
New(classTypeRef, vparamss.nestedMap(refOfDef))
515+
val nu = (makeNew(classTypeRef) /: vparamss) { (nu, vparams) =>
516+
val app = Apply(nu, vparams.map(refOfDef))
517+
vparams match {
518+
case vparam :: _ if vparam.mods.is(Given) => app.pushAttachment(ApplyGiven, ())
519+
case _ =>
520+
}
521+
app
522+
}
523+
ensureApplied(nu)
516524
}
517525

518526
val copiedAccessFlags = if (ctx.scala2Setting) EmptyFlags else AccessFlags

compiler/src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
5757
def unitLiteral(implicit ctx: Context): Literal =
5858
Literal(Constant(()))
5959

60+
def nullLiteral(implicit ctx: Context): Literal =
61+
Literal(Constant(null))
62+
6063
def New(tpt: Tree)(implicit ctx: Context): New =
6164
ta.assignType(untpd.New(tpt), tpt)
6265

@@ -496,7 +499,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
496499
else if (tpw isRef defn.DoubleClass) Literal(Constant(0d))
497500
else if (tpw isRef defn.ByteClass) Literal(Constant(0.toByte))
498501
else if (tpw isRef defn.ShortClass) Literal(Constant(0.toShort))
499-
else Literal(Constant(null)).select(defn.Any_asInstanceOf).appliedToType(tpe)
502+
else nullLiteral.select(defn.Any_asInstanceOf).appliedToType(tpe)
500503
}
501504

502505
private class FindLocalDummyAccumulator(cls: ClassSymbol)(implicit ctx: Context) extends TreeAccumulator[Symbol] {
@@ -915,7 +918,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
915918
Typed(tree, TypeTree(defn.AnyRefType))
916919
}
917920
else tree.ensureConforms(defn.ObjectType)
918-
receiver.select(defn.Object_ne).appliedTo(Literal(Constant(null)))
921+
receiver.select(defn.Object_ne).appliedTo(nullLiteral)
919922
}
920923

921924
/** If inititializer tree is `_', the default value of its type,

compiler/src/dotty/tools/dotc/ast/untpd.scala

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
6868
override def isType: Boolean = body.isType
6969
}
7070

71-
/** A function type with `implicit`, `erased`, or `contextual` modifiers */
71+
/** A function type with `implicit`, `erased`, or `given` modifiers */
7272
class FunctionWithMods(args: List[Tree], body: Tree, val mods: Modifiers)(implicit @constructorOnly src: SourceFile)
7373
extends Function(args, body)
7474

@@ -352,7 +352,13 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
352352
* navigation into these arguments from the IDE, and to do the right thing in
353353
* PrepareInlineable.
354354
*/
355-
def New(tpt: Tree, argss: List[List[Tree]])(implicit ctx: Context): Tree = {
355+
def New(tpt: Tree, argss: List[List[Tree]])(implicit ctx: Context): Tree =
356+
ensureApplied((makeNew(tpt) /: argss)(Apply(_, _)))
357+
358+
/** A new expression with constrictor and possibly type arguments. See
359+
* `New(tpt, argss)` for details.
360+
*/
361+
def makeNew(tpt: Tree)(implicit ctx: Context): Tree = {
356362
val (tycon, targs) = tpt match {
357363
case AppliedTypeTree(tycon, targs) =>
358364
(tycon, targs)
@@ -363,9 +369,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
363369
case _ =>
364370
(tpt, Nil)
365371
}
366-
var prefix: Tree = Select(New(tycon), nme.CONSTRUCTOR)
367-
if (targs.nonEmpty) prefix = TypeApply(prefix, targs)
368-
ensureApplied((prefix /: argss)(Apply(_, _)))
372+
val nu: Tree = Select(New(tycon), nme.CONSTRUCTOR)
373+
if (targs.nonEmpty) TypeApply(nu, targs) else nu
369374
}
370375

371376
def Block(stat: Tree, expr: Tree)(implicit src: SourceFile): Block =

compiler/src/dotty/tools/dotc/core/Flags.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,9 @@ object Flags {
394394
/** Symbol is an enum class or enum case (if used with case) */
395395
final val Enum: FlagSet = commonFlag(40, "<enum>")
396396

397+
/** An export forwarder */
398+
final val Exported: FlagSet = commonFlag(41, "exported")
399+
397400
/** Labeled with `erased` modifier (erased value) */
398401
final val Erased: FlagSet = termFlag(42, "erased")
399402

@@ -459,7 +462,7 @@ object Flags {
459462

460463
/** Flags representing source modifiers */
461464
private val CommonSourceModifierFlags: FlagSet =
462-
commonFlags(Private, Protected, Final, Case, Implicit, Implied, Override, JavaStatic)
465+
commonFlags(Private, Protected, Final, Case, Implicit, Implied, Given, Override, JavaStatic)
463466

464467
final val TypeSourceModifierFlags: FlagSet =
465468
CommonSourceModifierFlags.toTypeFlags | Abstract | Sealed | Opaque

compiler/src/dotty/tools/dotc/core/NameKinds.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ object NameKinds {
359359
val InlineAccessorName: PrefixNameKind = new PrefixNameKind(INLINEACCESSOR, "inline$")
360360

361361
val AvoidClashName: SuffixNameKind = new SuffixNameKind(AVOIDCLASH, "$_avoid_name_clash_$")
362+
val CacheName = new SuffixNameKind(CACHE, "$_cache")
362363
val DirectMethodName: SuffixNameKind = new SuffixNameKind(DIRECT, "$direct") { override def definesNewName = true }
363364
val FieldName: SuffixNameKind = new SuffixNameKind(FIELD, "$$local") {
364365
override def mkString(underlying: TermName, info: ThisInfo) = underlying.toString

compiler/src/dotty/tools/dotc/core/NameTags.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ object NameTags extends TastyFormat.NameTags {
3636
final val IMPLMETH = 32 // Used to define methods in implementation classes
3737
// (can probably be removed).
3838

39+
final val CACHE = 33 // Used as a cache for the rhs of an alias implicit.
40+
3941
def nameTagToString(tag: Int): String = tag match {
4042
case UTF8 => "UTF8"
4143
case QUALIFIED => "QUALIFIED"

compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ Standard-Section: "ASTs" TopLevelStat*
208208
EXTENSION -- An extension method
209209
GIVEN -- A new style implicit parameter, introduced with `given`
210210
PARAMsetter -- The setter part `x_=` of a var parameter `x` which itself is pickled as a PARAM
211+
EXPORTED -- An export forwarder
211212
Annotation
212213
213214
Annotation = ANNOTATION Length tycon_Type fullAnnotation_Term -- An annotation, given (class) type of constructor, and full application tree
@@ -329,6 +330,7 @@ object TastyFormat {
329330
final val GIVEN = 37
330331
final val IMPLIED = 38
331332
final val PARAMsetter = 39
333+
final val EXPORTED = 40
332334

333335
// Cat. 2: tag Nat
334336

@@ -457,7 +459,7 @@ object TastyFormat {
457459

458460
/** Useful for debugging */
459461
def isLegalTag(tag: Int): Boolean =
460-
firstSimpleTreeTag <= tag && tag <= PARAMsetter ||
462+
firstSimpleTreeTag <= tag && tag <= EXPORTED ||
461463
firstNatTreeTag <= tag && tag <= SYMBOLconst ||
462464
firstASTTreeTag <= tag && tag <= SINGLETONtpt ||
463465
firstNatASTTreeTag <= tag && tag <= NAMEDARG ||
@@ -500,6 +502,7 @@ object TastyFormat {
500502
| EXTENSION
501503
| GIVEN
502504
| PARAMsetter
505+
| EXPORTED
503506
| ANNOTATION
504507
| PRIVATEqualified
505508
| PROTECTEDqualified => true
@@ -560,6 +563,7 @@ object TastyFormat {
560563
case EXTENSION => "EXTENSION"
561564
case GIVEN => "GIVEN"
562565
case PARAMsetter => "PARAMsetter"
566+
case EXPORTED => "EXPORTED"
563567

564568
case SHAREDterm => "SHAREDterm"
565569
case SHAREDtype => "SHAREDtype"

compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,7 @@ class TreePickler(pickler: TastyPickler) {
656656
if (flags is Extension) writeByte(EXTENSION)
657657
if (flags is Given) writeByte(GIVEN)
658658
if (flags is ParamAccessor) writeByte(PARAMsetter)
659+
if (flags is Exported) writeByte(EXPORTED)
659660
assert(!(flags is Label))
660661
} else {
661662
if (flags is Sealed) writeByte(SEALED)

compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -632,8 +632,8 @@ class TreeUnpickler(reader: TastyReader,
632632
case STABLE => addFlag(StableRealizable)
633633
case EXTENSION => addFlag(Extension)
634634
case GIVEN => addFlag(Given)
635-
case PARAMsetter =>
636-
addFlag(ParamAccessor)
635+
case PARAMsetter => addFlag(ParamAccessor)
636+
case EXPORTED => addFlag(Exported)
637637
case PRIVATEqualified =>
638638
readByte()
639639
privateWithin = readWithin(ctx)

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
709709
val (leading, paramss) =
710710
if (isExtension && vparamss.nonEmpty) (paramsText(vparamss.head) ~ " " ~ txt, vparamss.tail)
711711
else (txt, vparamss)
712-
(txt /: paramss)((txt, params) => txt ~ paramsText(params))
712+
(txt /: paramss)((txt, params) =>
713+
txt ~
714+
(Str(" given ") provided params.nonEmpty && params.head.mods.is(Given)) ~
715+
paramsText(params))
713716
}
714717
protected def valDefToText[T >: Untyped](tree: ValDef[T]): Text = {
715718
import untpd.{modsDeco => _}
@@ -801,7 +804,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
801804
else if (suppressKw) PrintableFlags(isType) &~ Private
802805
else PrintableFlags(isType)
803806
if (homogenizedView && mods.flags.isTypeFlags) flagMask &~= ImplicitOrImplied // drop implicit/implied from classes
804-
val flags = (if (sym.exists) sym.flags else (mods.flags)) & flagMask
807+
val rawFlags = if (sym.exists) sym.flags else mods.flags
808+
if (rawFlags.is(Param)) flagMask = flagMask &~ Given
809+
val flags = rawFlags & flagMask
805810
val flagsText = if (flags.isEmpty) "" else keywordStr(flags.toString)
806811
val annotations =
807812
if (sym.exists) sym.annotations.filterNot(ann => dropAnnotForModText(ann.symbol)).map(_.tree)
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import MegaPhase._
5+
import core.DenotTransformers.{IdentityDenotTransformer}
6+
import core.Symbols._
7+
import core.Contexts._
8+
import core.Types._
9+
import core.Flags._
10+
import core.StdNames.nme
11+
import core.NameKinds.CacheName
12+
import core.Constants.Constant
13+
import core.Decorators._
14+
import core.TypeErasure.erasure
15+
import ast.tpd
16+
17+
object CacheAliasImplicits {
18+
val name: String = "cacheAliasImplicits"
19+
20+
/** Flags that disable caching */
21+
val NoCacheFlags =
22+
StableRealizable | // It's a simple forwarder, leave it as one
23+
Exported // Export forwarders are never cached
24+
}
25+
26+
/** This phase ensures that the right hand side of parameterless alias implicits
27+
* is cached. It applies to all alias implicits that have neither type parameters
28+
* nor a given clause. Example: The alias
29+
*
30+
* implicit a for TC = rhs
31+
*
32+
* is expanded before this phase
33+
*
34+
* implicit def a: TC = rhs
35+
*
36+
* It is then expanded further as follows:
37+
*
38+
* 1. If `rhs` is a simple name `x` (possibly with a `this.` prefix), leave the definition as is.
39+
* 2. Otherwise, if `rhs` is a pure path, replace the definition with
40+
*
41+
* implicit val a: TC = rhs
42+
*
43+
* 3. Otherwise, if `TC` is a reference type, replace the definition with
44+
*
45+
* private[this] var a$_cache: TC = null
46+
* implicit def a: TC = { if (a$_cache == null) a$_cache = rhs; a$_cache }
47+
*
48+
* 4. Otherwise `TC` is a value type. Replace the definition with
49+
*
50+
* lazy implicit val a: TC = rhs
51+
*/
52+
class CacheAliasImplicits extends MiniPhase with IdentityDenotTransformer { thisPhase =>
53+
import tpd._
54+
55+
override def phaseName: String = CacheAliasImplicits.name
56+
57+
override def transformDefDef(tree: DefDef)(implicit ctx: Context): Tree = {
58+
val sym = tree.symbol
59+
sym.info match {
60+
case ExprType(rhsType) if sym.is(Implied, butNot = CacheAliasImplicits.NoCacheFlags) =>
61+
// If rhs is a simple TermRef, leave a def.
62+
tree.rhs.tpe match {
63+
case TermRef(pre, _) =>
64+
pre match {
65+
case NoPrefix => return tree
66+
case pre: ThisType if pre.cls == ctx.owner.enclosingClass => return tree
67+
case _ =>
68+
}
69+
case _ =>
70+
}
71+
def makeVal(additionalFlags: FlagSet) = {
72+
sym.copySymDenotation(
73+
initFlags = sym.flags &~ Method | additionalFlags,
74+
info = rhsType)
75+
.installAfter(thisPhase)
76+
cpy.ValDef(tree)(tree.name, tree.tpt, tree.rhs)
77+
}
78+
if (isPurePath(tree.rhs)) makeVal(EmptyFlags)
79+
else if (rhsType.classSymbol.isValueClass ||
80+
!erasure(rhsType).typeSymbol.derivesFrom(defn.ObjectClass)) makeVal(Lazy)
81+
else {
82+
val cacheFlags = if (ctx.owner.isClass) Private | Local | Mutable else Mutable
83+
val cacheSym =
84+
ctx.newSymbol(ctx.owner, CacheName(tree.name), cacheFlags, rhsType, coord = sym.coord)
85+
if (ctx.owner.isClass) cacheSym.enteredAfter(thisPhase)
86+
val cacheDef = ValDef(cacheSym, tpd.defaultValue(rhsType))
87+
val cachingDef = cpy.DefDef(tree)(rhs =
88+
Block(
89+
If(
90+
ref(cacheSym).select(defn.Any_==).appliedTo(nullLiteral),
91+
Assign(ref(cacheSym), tree.rhs),
92+
unitLiteral) :: Nil,
93+
ref(cacheSym)
94+
)
95+
)
96+
Thicket(cacheDef, cachingDef)
97+
}
98+
case _ => tree
99+
}
100+
}
101+
}
102+
103+
104+

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ class Constructors extends MiniPhase with IdentityDenotTransformer { thisPhase =
235235
else {
236236
// insert test: if ($outer eq null) throw new NullPointerException
237237
val nullTest =
238-
If(ref(param).select(defn.Object_eq).appliedTo(Literal(Constant(null))),
238+
If(ref(param).select(defn.Object_eq).appliedTo(nullLiteral),
239239
Throw(New(defn.NullPointerExceptionClass.typeRef, Nil)),
240240
unitLiteral)
241241
nullTest :: assigns

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ object Erasure {
218218
adaptToType(tree, underlying)
219219
else if (!(tree.tpe <:< tycon)) {
220220
assert(!(tree.tpe.typeSymbol.isPrimitiveValueClass))
221-
val nullTree = Literal(Constant(null))
221+
val nullTree = nullLiteral
222222
val unboxedNull = adaptToType(nullTree, underlying)
223223

224224
evalOnce(tree) { t =>

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -189,14 +189,12 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
189189
holders:::stats
190190
}
191191

192-
private def nullOut(nullables: List[Symbol])(implicit ctx: Context): List[Tree] = {
193-
val nullConst = Literal(Constant(null))
192+
private def nullOut(nullables: List[Symbol])(implicit ctx: Context): List[Tree] =
194193
nullables.map { field =>
195194
assert(field.isField)
196195
field.setFlag(Mutable)
197-
ref(field).becomes(nullConst)
196+
ref(field).becomes(nullLiteral)
198197
}
199-
}
200198

201199
/** Create non-threadsafe lazy accessor equivalent to such code
202200
* ```
@@ -237,7 +235,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer {
237235
val targetRef = ref(target)
238236
val stats = targetRef.becomes(rhs) :: nullOut(nullableFor(sym))
239237
val init = If(
240-
targetRef.select(nme.eq).appliedTo(Literal(Constant(null))),
238+
targetRef.select(nme.eq).appliedTo(nullLiteral),
241239
Block(stats.init, stats.last),
242240
unitLiteral
243241
)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@ class Memoize extends MiniPhase with IdentityDenotTransformer { thisPhase =>
106106
val NoFieldNeeded = Lazy | Deferred | JavaDefined | (if (ctx.settings.YnoInline.value) EmptyFlags else Inline)
107107

108108
def erasedBottomTree(sym: Symbol) = {
109-
if (sym eq defn.NothingClass) Throw(Literal(Constant(null)))
110-
else if (sym eq defn.NullClass) Literal(Constant(null))
109+
if (sym eq defn.NothingClass) Throw(nullLiteral)
110+
else if (sym eq defn.NullClass) nullLiteral
111111
else if (sym eq defn.BoxedUnitClass) ref(defn.BoxedUnit_UNIT)
112112
else {
113113
assert(false, sym + " has no erased bottom tree")

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ object PatternMatcher {
361361
val unappPlan = if (defn.isBottomType(scrutinee.info)) {
362362
// Generate a throwaway but type-correct plan.
363363
// This plan will never execute because it'll be guarded by a `NonNullTest`.
364-
ResultPlan(tpd.Throw(tpd.Literal(Constant(null))))
364+
ResultPlan(tpd.Throw(tpd.nullLiteral))
365365
} else {
366366
val mt @ MethodType(_) = extractor.tpe.widen
367367
var unapp = extractor.appliedTo(ref(scrutinee).ensureConforms(mt.paramInfos.head))

compiler/src/dotty/tools/dotc/typer/Namer.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -981,14 +981,14 @@ class Namer { typer: Typer =>
981981
if (mbr.isType)
982982
ctx.newSymbol(
983983
cls, alias.toTypeName,
984-
Final,
984+
Exported | Final,
985985
fwdInfo(path.tpe.select(mbr.symbol), mbr.info),
986986
coord = span)
987987
else {
988988
val maybeStable = if (mbr.symbol.isStableMember) StableRealizable else EmptyFlags
989989
ctx.newSymbol(
990990
cls, alias,
991-
Method | Final | maybeStable | mbr.symbol.flags & ImplicitOrImplied,
991+
Exported | Method | Final | maybeStable | mbr.symbol.flags & ImplicitOrImplied,
992992
mbr.info.ensureMethodic,
993993
coord = span)
994994
}

0 commit comments

Comments
 (0)