Skip to content

Merge the IR node This into VarRef, with a magic LocalName. #5090

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1107,7 +1107,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
selfVarDef :: memberDefinitions
}

// After the super call, substitute `selfRef` for `This()`
// After the super call, substitute `selfRef` for `this`
val afterSuper = new ir.Transformers.Transformer {
override def transform(tree: js.Tree): js.Tree = tree match {
case js.This() =>
Expand Down Expand Up @@ -2430,7 +2430,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
tree match {
case js.Block(stats :+ expr) =>
js.Block(stats :+ exprToStat(expr))
case _:js.Literal | _:js.This | _:js.VarRef =>
case _:js.Literal | _:js.VarRef =>
js.Skip()
case _ =>
tree
Expand Down Expand Up @@ -4907,8 +4907,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
val newReceiver = genExpr(receiver)
val newArg = genStatOrExpr(arg, isStat)
newReceiver match {
case js.This() =>
// common case for which there is no side-effect nor NPE
case newReceiver: js.VarRef if !newReceiver.tpe.isNullable =>
// common case (notably for `this`) for which there is no side-effect nor NPE
newArg
case _ =>
js.Block(
Expand Down
13 changes: 7 additions & 6 deletions ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -497,12 +497,13 @@ object Hashers {
mixTypeRef(typeRef)

case VarRef(name) =>
mixTag(TagVarRef)
mixName(name)
mixType(tree.tpe)

case This() =>
mixTag(TagThis)
if (name.isThis) {
// "Optimized" representation, like in Serializers
mixTag(TagThis)
} else {
mixTag(TagVarRef)
mixName(name)
}
mixType(tree.tpe)

case Closure(arrow, captureParams, params, restParam, body, captureValues) =>
Expand Down
30 changes: 26 additions & 4 deletions ir/shared/src/main/scala/org/scalajs/ir/Names.scala
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ object Names {
*
* Local names must be non-empty, and can contain any Unicode code point
* except `/ . ; [`.
*
* As an exception, the local name `".this"` represents the `this` binding.
* It cannot be used to declare variables (in `VarDef`s or `ParamDef`s) but
* can be referred to with a `VarRef`.
*/
final class LocalName private (encoded: UTF8String)
extends Name(encoded) with Comparable[LocalName] {
Expand All @@ -79,22 +83,40 @@ object Names {

protected def stringPrefix: String = "LocalName"

final def withPrefix(prefix: LocalName): LocalName =
final def isThis: Boolean =
this eq LocalName.This

final def withPrefix(prefix: LocalName): LocalName = {
require(!isThis && !prefix.isThis, "cannot concatenate LocalName.This")
new LocalName(prefix.encoded ++ this.encoded)
}

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

final def withSuffix(suffix: LocalName): LocalName =
final def withSuffix(suffix: LocalName): LocalName = {
require(!isThis && !suffix.isThis, "cannot concatenate LocalName.This")
new LocalName(this.encoded ++ suffix.encoded)
}

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

object LocalName {
def apply(name: UTF8String): LocalName =
new LocalName(validateSimpleEncodedName(name))
private final val ThisEncodedName: UTF8String =
UTF8String(".this")

/** The unique `LocalName` with encoded name `.this`. */
val This: LocalName =
new LocalName(ThisEncodedName)

def apply(name: UTF8String): LocalName = {
if (UTF8String.equals(name, ThisEncodedName))
This
else
new LocalName(validateSimpleEncodedName(name))
}

def apply(name: String): LocalName =
LocalName(UTF8String(name))
Expand Down
9 changes: 4 additions & 5 deletions ir/shared/src/main/scala/org/scalajs/ir/Printers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,6 @@ object Printers {
case JSPrivateSelect(qual, _) => containsOnlySelectsFromAtom(qual)
case JSSelect(qual, _) => containsOnlySelectsFromAtom(qual)
case VarRef(_) => true
case This() => true
case _ => false // in particular, Apply
}
if (containsOnlySelectsFromAtom(ctor)) {
Expand Down Expand Up @@ -844,10 +843,10 @@ object Printers {
// Atomic expressions

case VarRef(name) =>
print(name)

case This() =>
print("this")
if (name.isThis)
print("this")
else
print(name)

case Closure(arrow, captureParams, params, restParam, body, captureValues) =>
if (arrow)
Expand Down
24 changes: 14 additions & 10 deletions ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -524,12 +524,13 @@ object Serializers {
writeTypeRef(typeRef)

case VarRef(name) =>
writeTagAndPos(TagVarRef)
writeName(name)
writeType(tree.tpe)

case This() =>
writeTagAndPos(TagThis)
if (name.isThis) {
// "Optimized" representation that is compatible with IR < 1.18
writeTagAndPos(TagThis)
} else {
writeTagAndPos(TagVarRef)
writeName(name)
}
writeType(tree.tpe)

case Closure(arrow, captureParams, params, restParam, body, captureValues) =>
Expand Down Expand Up @@ -1155,10 +1156,13 @@ object Serializers {
if (hacks.use13) {
val cls = readClassName()
val rhs = readTree()
if (cls != enclosingClassName || !rhs.isInstanceOf[This]) {
throw new IOException(
s"Illegal legacy StoreModule(${cls.nameString}, $rhs) " +
s"found in class ${enclosingClassName.nameString}")
rhs match {
case This() if cls == enclosingClassName =>
// ok
case _ =>
throw new IOException(
s"Illegal legacy StoreModule(${cls.nameString}, $rhs) " +
s"found in class ${enclosingClassName.nameString}")
}
}
StoreModule()
Expand Down
2 changes: 1 addition & 1 deletion ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ object Transformers {
case _:Skip | _:Debugger | _:LoadModule | _:StoreModule |
_:SelectStatic | _:SelectJSNativeMember | _:LoadJSConstructor |
_:LoadJSModule | _:JSNewTarget | _:JSImportMeta |
_:Literal | _:VarRef | _:This | _:JSGlobalRef | _:LinkTimeProperty =>
_:Literal | _:VarRef | _:JSGlobalRef | _:LinkTimeProperty =>
tree
}
}
Expand Down
2 changes: 1 addition & 1 deletion ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ object Traversers {
case _:Skip | _:Debugger | _:LoadModule | _:StoreModule |
_:SelectStatic | _:SelectJSNativeMember | _:LoadJSConstructor |
_:LoadJSModule | _:JSNewTarget | _:JSImportMeta |
_:Literal | _:VarRef | _:This | _:JSGlobalRef | _:LinkTimeProperty =>
_:Literal | _:VarRef | _:JSGlobalRef | _:LinkTimeProperty =>
}

def traverseClassDef(tree: ClassDef): Unit = {
Expand Down
10 changes: 8 additions & 2 deletions ir/shared/src/main/scala/org/scalajs/ir/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1081,8 +1081,14 @@ object Trees {
sealed case class VarRef(name: LocalName)(val tpe: Type)(
implicit val pos: Position) extends AssignLhs

sealed case class This()(val tpe: Type)(implicit val pos: Position)
extends Tree
/** Convenience constructor and extractor for `VarRef`s representing `this` bindings. */
object This {
def apply()(tpe: Type)(implicit pos: Position): VarRef =
VarRef(LocalName.This)(tpe)

def unapply(tree: VarRef): Boolean =
tree.name.isThis
}

/** Closure with explicit captures.
*
Expand Down
3 changes: 0 additions & 3 deletions ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -863,9 +863,6 @@ class PrintersTest {

@Test def printVarRef(): Unit = {
assertPrintEquals("x", VarRef("x")(IntType))
}

@Test def printThis(): Unit = {
assertPrintEquals("this", This()(AnyType))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1261,7 +1261,6 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) {
def test(tree: Tree): Boolean = tree match {
// Atomic expressions
case _: Literal => true
case _: This => true
case _: JSNewTarget => true
case _: LinkTimeProperty => true

Expand Down Expand Up @@ -2468,18 +2467,18 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) {
genCallHelper(VarField.systemIdentityHashCode, newLhs)

case WrapAsThrowable =>
val newLhsVar = newLhs.asInstanceOf[js.VarRef]
assert(newLhs.isInstanceOf[js.VarRef] || newLhs.isInstanceOf[js.This], newLhs)
js.If(
genIsInstanceOfClass(newLhsVar, ThrowableClass),
newLhsVar,
genScalaClassNew(JavaScriptExceptionClass, AnyArgConstructorName, newLhsVar))
genIsInstanceOfClass(newLhs, ThrowableClass),
newLhs,
genScalaClassNew(JavaScriptExceptionClass, AnyArgConstructorName, newLhs))

case UnwrapFromThrowable =>
val newLhsVar = newLhs.asInstanceOf[js.VarRef]
assert(newLhs.isInstanceOf[js.VarRef] || newLhs.isInstanceOf[js.This], newLhs)
js.If(
genIsInstanceOfClass(newLhsVar, JavaScriptExceptionClass),
genSelect(newLhsVar, FieldIdent(exceptionFieldName)),
newLhsVar)
genIsInstanceOfClass(newLhs, JavaScriptExceptionClass),
genSelect(newLhs, FieldIdent(exceptionFieldName)),
newLhs)
}

case BinaryOp(op, lhs, rhs) =>
Expand Down Expand Up @@ -2519,7 +2518,10 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) {
* that the body of `Object.equals__O__Z` can be compiled as
* `this === that` instead of `Object.is(this, that)`.
*/
!tree.isInstanceOf[This]
tree match {
case This() => false
case _ => true
}
case ClassType(BoxedByteClass | BoxedShortClass |
BoxedIntegerClass | BoxedFloatClass | BoxedDoubleClass, _) =>
true
Expand Down Expand Up @@ -3021,12 +3023,6 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) {
case Transient(JSVarRef(name, _)) =>
js.VarRef(name)

case This() =>
if (env.hasExplicitThis)
fileLevelVar(VarField.thiz)
else
js.This()

case tree: Closure =>
transformClosure(tree)

Expand Down Expand Up @@ -3180,12 +3176,6 @@ private[emitter] class FunctionEmitter(sjsGen: SJSGen) {
VarKind.Immutable
}

case This() if env.hasExplicitThis =>
VarKind.ExplicitThisAlias

case This() if permitImplicitJSThisCapture =>
VarKind.ThisAlias

case _ =>
explicitCapture()
VarKind.Immutable
Expand Down Expand Up @@ -3415,7 +3405,6 @@ private object FunctionEmitter {
// Environment

final class Env private (
val hasExplicitThis: Boolean,
val expectedReturnType: Type,
val enclosingClassName: Option[ClassName],
vars: Map[LocalName, VarKind],
Expand Down Expand Up @@ -3448,7 +3437,7 @@ private object FunctionEmitter {
copy(enclosingClassName = enclosingClassName)

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

def withVars(newVars: Map[LocalName, VarKind]): Env =
copy(vars = vars ++ newVars)
Expand Down Expand Up @@ -3483,7 +3472,6 @@ private object FunctionEmitter {
copy(inLoopForVarCapture = inLoopForVarCapture)

private def copy(
hasExplicitThis: Boolean = this.hasExplicitThis,
expectedReturnType: Type = this.expectedReturnType,
enclosingClassName: Option[ClassName] = this.enclosingClassName,
vars: Map[LocalName, VarKind] = this.vars,
Expand All @@ -3492,15 +3480,18 @@ private object FunctionEmitter {
defaultBreakTargets: Set[LabelName] = this.defaultBreakTargets,
defaultContinueTargets: Set[LabelName] = this.defaultContinueTargets,
inLoopForVarCapture: Boolean = this.inLoopForVarCapture): Env = {
new Env(hasExplicitThis, expectedReturnType, enclosingClassName, vars,
new Env(expectedReturnType, enclosingClassName, vars,
labeledExprLHSes, labelsTurnedIntoContinue, defaultBreakTargets,
defaultContinueTargets, inLoopForVarCapture)
}
}

object Env {
private val InitVars: Map[LocalName, VarKind] =
Map(LocalName.This -> VarKind.ThisAlias)

def empty(expectedReturnType: Type): Env = {
new Env(false, expectedReturnType, None, Map.empty, Map.empty, Set.empty,
new Env(expectedReturnType, None, InitVars, Map.empty, Set.empty,
Set.empty, Set.empty, false)
}
}
Expand Down
Loading
Loading