Skip to content

Commit 29c4439

Browse files
committed
Merge the IR node This into VarRef, with a magic LocalName.
Everywhere we handle `VarRef`s, we applied the same treatment to `This` nodes. In some places, like in the optimizer's `Binding`s, we also had an additional abstraction to represent either an actual `VarRef` or a `this` value. We now merge `This` as a particular case of `VarRef`. We use a magic `LocalName` that is otherwise invalid, namely `.this`. A variable of that name cannot be declared, either in a `ParamDef` or in a `VarDef`. It is only introduced for receivers, and is always immutable. Several areas of the code get simpler with the merge. The optimizer's `Binding`s is the most obvious example. The `FunctionEmitter`s also benefit from the changes. Other areas get nothing but a reduction of alternatives in pattern matches. `this` values still hold particular meaning in many situations. Therefore, we keep a source compatible constructor/extractor object for `This()`.
1 parent 4878731 commit 29c4439

File tree

20 files changed

+284
-208
lines changed

20 files changed

+284
-208
lines changed

compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,7 +1107,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
11071107
selfVarDef :: memberDefinitions
11081108
}
11091109

1110-
// After the super call, substitute `selfRef` for `This()`
1110+
// After the super call, substitute `selfRef` for `this`
11111111
val afterSuper = new ir.Transformers.Transformer {
11121112
override def transform(tree: js.Tree): js.Tree = tree match {
11131113
case js.This() =>
@@ -2430,7 +2430,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
24302430
tree match {
24312431
case js.Block(stats :+ expr) =>
24322432
js.Block(stats :+ exprToStat(expr))
2433-
case _:js.Literal | _:js.This | _:js.VarRef =>
2433+
case _:js.Literal | _:js.VarRef =>
24342434
js.Skip()
24352435
case _ =>
24362436
tree
@@ -4904,8 +4904,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
49044904
val newReceiver = genExpr(receiver)
49054905
val newArg = genStatOrExpr(arg, isStat)
49064906
newReceiver match {
4907-
case js.This() =>
4908-
// common case for which there is no side-effect nor NPE
4907+
case newReceiver: js.VarRef if !newReceiver.tpe.isNullable =>
4908+
// common case (notably for `this`) for which there is no side-effect nor NPE
49094909
newArg
49104910
case _ =>
49114911
js.Block(

ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -525,12 +525,13 @@ object Hashers {
525525
mixTypeRef(typeRef)
526526

527527
case VarRef(name) =>
528-
mixTag(TagVarRef)
529-
mixName(name)
530-
mixType(tree.tpe)
531-
532-
case This() =>
533-
mixTag(TagThis)
528+
if (name.isThis) {
529+
// "Optimized" representation, like in Serializers
530+
mixTag(TagThis)
531+
} else {
532+
mixTag(TagVarRef)
533+
mixName(name)
534+
}
534535
mixType(tree.tpe)
535536

536537
case Closure(arrow, captureParams, params, restParam, body, captureValues) =>

ir/shared/src/main/scala/org/scalajs/ir/Names.scala

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ object Names {
6464
*
6565
* Local names must be non-empty, and can contain any Unicode code point
6666
* except `/ . ; [`.
67+
*
68+
* As an exception, the local name `".this"` represents the `this` binding.
69+
* It cannot be used to declare variables (in `VarDef`s or `ParamDef`s) but
70+
* can be referred to with a `VarRef`.
6771
*/
6872
final class LocalName private (encoded: UTF8String)
6973
extends Name(encoded) with Comparable[LocalName] {
@@ -79,22 +83,38 @@ object Names {
7983

8084
protected def stringPrefix: String = "LocalName"
8185

86+
final def isThis: Boolean =
87+
this eq LocalName.This
88+
8289
final def withPrefix(prefix: LocalName): LocalName =
83-
new LocalName(prefix.encoded ++ this.encoded)
90+
if (isThis || prefix.isThis) LocalName(prefix.encoded ++ this.encoded) // will fail
91+
else new LocalName(prefix.encoded ++ this.encoded) // always valid; skip check
8492

8593
final def withPrefix(prefix: String): LocalName =
8694
LocalName(UTF8String(prefix) ++ this.encoded)
8795

8896
final def withSuffix(suffix: LocalName): LocalName =
89-
new LocalName(this.encoded ++ suffix.encoded)
97+
if (isThis || suffix.isThis) LocalName(this.encoded ++ suffix.encoded) // will fail
98+
else new LocalName(this.encoded ++ suffix.encoded) // always valid; skip check
9099

91100
final def withSuffix(suffix: String): LocalName =
92101
LocalName(this.encoded ++ UTF8String(suffix))
93102
}
94103

95104
object LocalName {
96-
def apply(name: UTF8String): LocalName =
97-
new LocalName(validateSimpleEncodedName(name))
105+
private final val ThisEncodedName: UTF8String =
106+
UTF8String(".this")
107+
108+
/** The unique `LocalName` with encoded name `.this`. */
109+
val This: LocalName =
110+
new LocalName(ThisEncodedName)
111+
112+
def apply(name: UTF8String): LocalName = {
113+
if (name.length == 5 && name(0) == '.' && UTF8String.equals(name, ThisEncodedName))
114+
This
115+
else
116+
new LocalName(validateSimpleEncodedName(name))
117+
}
98118

99119
def apply(name: String): LocalName =
100120
LocalName(UTF8String(name))

ir/shared/src/main/scala/org/scalajs/ir/Printers.scala

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,6 @@ object Printers {
595595
case JSPrivateSelect(qual, _) => containsOnlySelectsFromAtom(qual)
596596
case JSSelect(qual, _) => containsOnlySelectsFromAtom(qual)
597597
case VarRef(_) => true
598-
case This() => true
599598
case _ => false // in particular, Apply
600599
}
601600
if (containsOnlySelectsFromAtom(ctor)) {
@@ -869,10 +868,10 @@ object Printers {
869868
// Atomic expressions
870869

871870
case VarRef(name) =>
872-
print(name)
873-
874-
case This() =>
875-
print("this")
871+
if (name.isThis)
872+
print("this")
873+
else
874+
print(name)
876875

877876
case Closure(arrow, captureParams, params, restParam, body, captureValues) =>
878877
if (arrow)

ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -552,12 +552,13 @@ object Serializers {
552552
writeTypeRef(typeRef)
553553

554554
case VarRef(name) =>
555-
writeTagAndPos(TagVarRef)
556-
writeName(name)
557-
writeType(tree.tpe)
558-
559-
case This() =>
560-
writeTagAndPos(TagThis)
555+
if (name.isThis) {
556+
// "Optimized" representation that is compatible with IR < 1.18
557+
writeTagAndPos(TagThis)
558+
} else {
559+
writeTagAndPos(TagVarRef)
560+
writeName(name)
561+
}
561562
writeType(tree.tpe)
562563

563564
case Closure(arrow, captureParams, params, restParam, body, captureValues) =>
@@ -1190,10 +1191,13 @@ object Serializers {
11901191
if (hacks.use13) {
11911192
val cls = readClassName()
11921193
val rhs = readTree()
1193-
if (cls != enclosingClassName || !rhs.isInstanceOf[This]) {
1194-
throw new IOException(
1195-
s"Illegal legacy StoreModule(${cls.nameString}, $rhs) " +
1196-
s"found in class ${enclosingClassName.nameString}")
1194+
rhs match {
1195+
case This() if cls == enclosingClassName =>
1196+
// ok
1197+
case _ =>
1198+
throw new IOException(
1199+
s"Illegal legacy StoreModule(${cls.nameString}, $rhs) " +
1200+
s"found in class ${enclosingClassName.nameString}")
11971201
}
11981202
}
11991203
StoreModule()

ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ object Transformers {
216216
case _:Skip | _:Debugger | _:LoadModule | _:StoreModule |
217217
_:SelectStatic | _:SelectJSNativeMember | _:LoadJSConstructor |
218218
_:LoadJSModule | _:JSNewTarget | _:JSImportMeta |
219-
_:Literal | _:VarRef | _:This | _:JSGlobalRef | _:LinkTimeProperty =>
219+
_:Literal | _:VarRef | _:JSGlobalRef | _:LinkTimeProperty =>
220220
tree
221221
}
222222
}

ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ object Traversers {
222222
case _:Skip | _:Debugger | _:LoadModule | _:StoreModule |
223223
_:SelectStatic | _:SelectJSNativeMember | _:LoadJSConstructor |
224224
_:LoadJSModule | _:JSNewTarget | _:JSImportMeta |
225-
_:Literal | _:VarRef | _:This | _:JSGlobalRef | _:LinkTimeProperty =>
225+
_:Literal | _:VarRef | _:JSGlobalRef | _:LinkTimeProperty =>
226226
}
227227

228228
def traverseClassDef(tree: ClassDef): Unit = {

ir/shared/src/main/scala/org/scalajs/ir/Trees.scala

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,8 +1088,14 @@ object Trees {
10881088
sealed case class VarRef(name: LocalName)(val tpe: Type)(
10891089
implicit val pos: Position) extends AssignLhs
10901090

1091-
sealed case class This()(val tpe: Type)(implicit val pos: Position)
1092-
extends Tree
1091+
/** Convenience constructor and extractor for `VarRef`s representing `this` bindings. */
1092+
object This {
1093+
def apply()(tpe: Type)(implicit pos: Position): VarRef =
1094+
VarRef(LocalName.This)(tpe)
1095+
1096+
def unapply(tree: VarRef): Boolean =
1097+
tree.name.isThis
1098+
}
10931099

10941100
/** Closure with explicit captures.
10951101
*

ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -884,9 +884,6 @@ class PrintersTest {
884884

885885
@Test def printVarRef(): Unit = {
886886
assertPrintEquals("x", VarRef("x")(IntType))
887-
}
888-
889-
@Test def printThis(): Unit = {
890887
assertPrintEquals("this", This()(AnyType))
891888
}
892889

linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1245,7 +1245,6 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) {
12451245
def test(tree: Tree): Boolean = tree match {
12461246
// Atomic expressions
12471247
case _: Literal => true
1248-
case _: This => true
12491248
case _: JSNewTarget => true
12501249
case _: LinkTimeProperty => true
12511250

@@ -2468,7 +2467,10 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) {
24682467
* that the body of `Object.equals__O__Z` can be compiled as
24692468
* `this === that` instead of `Object.is(this, that)`.
24702469
*/
2471-
!tree.isInstanceOf[This]
2470+
tree match {
2471+
case This() => false
2472+
case _ => true
2473+
}
24722474
case ClassType(BoxedByteClass | BoxedShortClass |
24732475
BoxedIntegerClass | BoxedFloatClass | BoxedDoubleClass, _) =>
24742476
true
@@ -2807,14 +2809,16 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) {
28072809
genCallHelper(VarField.systemIdentityHashCode, transformExprNoChar(expr))
28082810

28092811
case WrapAsThrowable(expr) =>
2810-
val newExpr = transformExprNoChar(expr).asInstanceOf[js.VarRef]
2812+
val newExpr = transformExprNoChar(expr)
2813+
assert(newExpr.isInstanceOf[js.VarRef] || newExpr.isInstanceOf[js.This], newExpr)
28112814
js.If(
28122815
genIsInstanceOfClass(newExpr, ThrowableClass),
28132816
newExpr,
28142817
genScalaClassNew(JavaScriptExceptionClass, AnyArgConstructorName, newExpr))
28152818

28162819
case UnwrapFromThrowable(expr) =>
2817-
val newExpr = transformExprNoChar(expr).asInstanceOf[js.VarRef]
2820+
val newExpr = transformExprNoChar(expr)
2821+
assert(newExpr.isInstanceOf[js.VarRef] || newExpr.isInstanceOf[js.This], newExpr)
28182822
js.If(
28192823
genIsInstanceOfClass(newExpr, JavaScriptExceptionClass),
28202824
genSelect(newExpr, FieldIdent(exceptionFieldName)),
@@ -3032,12 +3036,6 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) {
30323036
case Transient(JSVarRef(name, _)) =>
30333037
js.VarRef(name)
30343038

3035-
case This() =>
3036-
if (env.hasExplicitThis)
3037-
fileLevelVar(VarField.thiz)
3038-
else
3039-
js.This()
3040-
30413039
case tree: Closure =>
30423040
transformClosure(tree)
30433041

@@ -3191,12 +3189,6 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) {
31913189
VarKind.Immutable
31923190
}
31933191

3194-
case This() if env.hasExplicitThis =>
3195-
VarKind.ExplicitThisAlias
3196-
3197-
case This() if permitImplicitJSThisCapture =>
3198-
VarKind.ThisAlias
3199-
32003192
case _ =>
32013193
explicitCapture()
32023194
VarKind.Immutable
@@ -3426,7 +3418,6 @@ private object FunctionEmitter {
34263418
// Environment
34273419

34283420
final class Env private (
3429-
val hasExplicitThis: Boolean,
34303421
val expectedReturnType: Type,
34313422
val enclosingClassName: Option[ClassName],
34323423
vars: Map[LocalName, VarKind],
@@ -3459,7 +3450,7 @@ private object FunctionEmitter {
34593450
copy(enclosingClassName = enclosingClassName)
34603451

34613452
def withExplicitThis(): Env =
3462-
copy(hasExplicitThis = true)
3453+
copy(vars = vars + (LocalName.This -> VarKind.ExplicitThisAlias))
34633454

34643455
def withVars(newVars: Map[LocalName, VarKind]): Env =
34653456
copy(vars = vars ++ newVars)
@@ -3494,7 +3485,6 @@ private object FunctionEmitter {
34943485
copy(inLoopForVarCapture = inLoopForVarCapture)
34953486

34963487
private def copy(
3497-
hasExplicitThis: Boolean = this.hasExplicitThis,
34983488
expectedReturnType: Type = this.expectedReturnType,
34993489
enclosingClassName: Option[ClassName] = this.enclosingClassName,
35003490
vars: Map[LocalName, VarKind] = this.vars,
@@ -3503,15 +3493,18 @@ private object FunctionEmitter {
35033493
defaultBreakTargets: Set[LabelName] = this.defaultBreakTargets,
35043494
defaultContinueTargets: Set[LabelName] = this.defaultContinueTargets,
35053495
inLoopForVarCapture: Boolean = this.inLoopForVarCapture): Env = {
3506-
new Env(hasExplicitThis, expectedReturnType, enclosingClassName, vars,
3496+
new Env(expectedReturnType, enclosingClassName, vars,
35073497
labeledExprLHSes, labelsTurnedIntoContinue, defaultBreakTargets,
35083498
defaultContinueTargets, inLoopForVarCapture)
35093499
}
35103500
}
35113501

35123502
object Env {
3503+
private val InitVars: Map[LocalName, VarKind] =
3504+
Map(LocalName.This -> VarKind.ThisAlias)
3505+
35133506
def empty(expectedReturnType: Type): Env = {
3514-
new Env(false, expectedReturnType, None, Map.empty, Map.empty, Set.empty,
3507+
new Env(expectedReturnType, None, InitVars, Map.empty, Set.empty,
35153508
Set.empty, Set.empty, false)
35163509
}
35173510
}

0 commit comments

Comments
 (0)