diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index a64d6a8656..5d393dfa47 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -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() => @@ -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 @@ -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( diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala index 770d83a7c7..4f6c3eeb72 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala @@ -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) => diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Names.scala b/ir/shared/src/main/scala/org/scalajs/ir/Names.scala index 8193227be0..d9bee3518b 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Names.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Names.scala @@ -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] { @@ -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)) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala index d6a490b634..5c606e81ee 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala @@ -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)) { @@ -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) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala index 66c3249f9e..74e7d2c1aa 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala @@ -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) => @@ -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() diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala index 7be426e058..82617dfd01 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala @@ -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 } } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala index d242bbfce7..3edb2ac178 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala @@ -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 = { diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala index 6cd4db23c7..7fc574d991 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala @@ -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. * diff --git a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala index 0b2cb5a9b6..6f68760422 100644 --- a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala +++ b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala @@ -863,9 +863,6 @@ class PrintersTest { @Test def printVarRef(): Unit = { assertPrintEquals("x", VarRef("x")(IntType)) - } - - @Test def printThis(): Unit = { assertPrintEquals("this", This()(AnyType)) } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala index 12af2ad9ab..df5ed07b70 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala @@ -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 @@ -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) => @@ -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 @@ -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) @@ -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 @@ -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], @@ -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) @@ -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, @@ -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) } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala index bf0e54adc5..7ef7a87ac3 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala @@ -209,9 +209,12 @@ object FunctionEmitter { Some(VarStorage.Local(newTargetParam)) } - val receiverStorage = receiverType.map { tpe => - val receiverParam = fb.addParam(receiverOriginalName, tpe) - VarStorage.Local(receiverParam) + val receiverEnv: Env = receiverType match { + case None => + Map.empty + case Some(tpe) => + val receiverParam = fb.addParam(receiverOriginalName, tpe) + Map(LocalName.This -> VarStorage.Local(receiverParam)) } val normalParamsEnv: Env = paramDefs.map { paramDef => @@ -222,7 +225,7 @@ object FunctionEmitter { paramDef.name.name -> VarStorage.Local(param) }.toMap - val fullEnv: Env = captureParamsEnv ++ preSuperEnvEnv ++ normalParamsEnv + val fullEnv: Env = captureParamsEnv ++ preSuperEnvEnv ++ receiverEnv ++ normalParamsEnv fb.setResultTypes(resultTypes) @@ -230,7 +233,6 @@ object FunctionEmitter { fb, enclosingClassName, newTargetStorage, - receiverStorage, fullEnv ) } @@ -275,7 +277,6 @@ private class FunctionEmitter private ( val fb: FunctionBuilder, enclosingClassName: Option[ClassName], _newTargetStorage: Option[FunctionEmitter.VarStorage.Local], - _receiverStorage: Option[FunctionEmitter.VarStorage.Local], paramsEnv: FunctionEmitter.Env )(implicit ctx: WasmContext) { import FunctionEmitter._ @@ -290,9 +291,6 @@ private class FunctionEmitter private ( private def newTargetStorage: VarStorage.Local = _newTargetStorage.getOrElse(throw new Error("Cannot access new.target in this context.")) - private def receiverStorage: VarStorage.Local = - _receiverStorage.getOrElse(throw new Error("Cannot access to the receiver in this context.")) - /** Opens a new scope in which NPEs can be thrown by jumping to the NPE label. * * When NPEs are unchecked this is a no-op (other than calling `body`). @@ -530,7 +528,6 @@ private class FunctionEmitter private ( case t: VarRef => genVarRef(t) case t: LoadModule => genLoadModule(t) case t: StoreModule => genStoreModule(t) - case t: This => genThis(t) case t: ApplyStatically => genApplyStatically(t) case t: Apply => genApply(t) case t: ApplyStatic => genApplyStatic(t) @@ -1347,9 +1344,8 @@ private class FunctionEmitter private ( throw new AssertionError(s"Cannot emit $tree at ${tree.pos} without enclosing class name") } - genTreeAuto(This()(ClassType(className, nullable = false))(tree.pos)) - markPosition(tree) + genReadStorage(lookupLocal(LocalName.This)) fb += wa.GlobalSet(genGlobalID.forModuleInstance(className)) VoidType } @@ -2421,12 +2417,6 @@ private class FunctionEmitter private ( tree.tpe } - private def genThis(tree: This): Type = { - markPosition(tree) - genReadStorage(receiverStorage) - tree.tpe - } - private def genVarDef(tree: VarDef): Type = { /* This is an isolated VarDef that is not in a Block. * Its scope is empty by construction, and therefore it need not be stored. diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala index 1e75d4db31..103d19f963 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/ClassDefChecker.scala @@ -150,8 +150,10 @@ private final class ClassDefChecker(classDef: ClassDef, } { implicit val ctx = ErrorContext(p) val name = ident.name + if (name.isThis) + reportError(i"Illegal JS class capture with name '$name'") if (!alreadyDeclared.add(name)) - reportError(i"Duplicate JS class capture '$name'") + reportError(i"Duplicate JS class capture '$name'") if (tpe == VoidType) reportError(i"The JS class capture $name cannot have type VoidType") if (mutable) @@ -300,9 +302,8 @@ private final class ClassDefChecker(classDef: ClassDef, // Body for (body <- optBody) { - val thisType = if (static) VoidType else instanceThisType val bodyEnv = Env.fromParams(params) - .withThisType(thisType) + .withMaybeThisType(!static, instanceThisType) if (isConstructor) checkConstructorBody(body, bodyEnv) @@ -357,9 +358,8 @@ private final class ClassDefChecker(classDef: ClassDef, checkExportedPropertyName(pName) checkJSParamDefs(params, restParam) - val thisType = if (static) VoidType else instanceThisType - val env = - Env.fromParams(classDef.jsClassCaptures.getOrElse(Nil) ++ params ++ restParam).withThisType(thisType) + val env = Env.fromParams(classDef.jsClassCaptures.getOrElse(Nil) ++ params ++ restParam) + .withMaybeThisType(!static, instanceThisType) checkTree(body, env) } @@ -383,11 +383,11 @@ private final class ClassDefChecker(classDef: ClassDef, checkExportedPropertyName(pName) val jsClassCaptures = classDef.jsClassCaptures.getOrElse(Nil) - val thisType = if (static) VoidType else instanceThisType getterBody.foreach { body => withPerMethodState { - val bodyEnv = Env.fromParams(jsClassCaptures).withThisType(thisType) + val bodyEnv = Env.fromParams(jsClassCaptures) + .withMaybeThisType(!static, instanceThisType) checkTree(body, bodyEnv) } } @@ -395,7 +395,8 @@ private final class ClassDefChecker(classDef: ClassDef, setterArgAndBody.foreach { case (setterArg, body) => withPerMethodState { checkJSParamDefs(setterArg :: Nil, None) - val bodyEnv = Env.fromParams(jsClassCaptures :+ setterArg).withThisType(thisType) + val bodyEnv = Env.fromParams(jsClassCaptures :+ setterArg) + .withMaybeThisType(!static, instanceThisType) checkTree(body, bodyEnv) } } @@ -948,13 +949,7 @@ private final class ClassDefChecker(classDef: ClassDef, if (tree.tpe != localDef.tpe) reportError(i"Variable $name of type ${localDef.tpe} typed as ${tree.tpe}") } - - case This() => - if (env.thisType == VoidType) - reportError(i"Cannot find `this` in scope") - else if (tree.tpe != env.thisType) - reportError(i"`this` of type ${env.thisType} typed as ${tree.tpe}") - if (env.isThisRestricted) + if (env.isThisRestricted && name.isThis) reportError(i"Restricted use of `this` before the super constructor call") case Closure(arrow, captureParams, params, restParam, body, captureValues) => @@ -984,7 +979,7 @@ private final class ClassDefChecker(classDef: ClassDef, val bodyEnv = Env .fromParams(captureParams ++ params ++ restParam) .withHasNewTarget(!arrow) - .withThisType(if (arrow) VoidType else AnyType) + .withMaybeThisType(!arrow, AnyType) checkTree(body, bodyEnv) } @@ -1023,6 +1018,8 @@ private final class ClassDefChecker(classDef: ClassDef, private def checkDeclareLocalVar(ident: LocalIdent)( implicit ctx: ErrorContext): Unit = { + if (ident.name.isThis) + reportError(i"Illegal definition of a variable with name ${ident.name}") if (!declaredLocalVarNamesPerMethod.add(ident.name)) reportError(i"Duplicate local variable name ${ident.name}.") } @@ -1072,8 +1069,6 @@ object ClassDefChecker { private class Env( /** Whether there is a valid `new.target` in scope. */ val hasNewTarget: Boolean, - /** The type of `this` in scope, or `VoidType` if there is no `this` in scope. */ - val thisType: Type, /** Local variables in scope (including through closures). */ val locals: Map[LocalName, LocalDef], /** Return types by label. */ @@ -1087,7 +1082,11 @@ object ClassDefChecker { copy(hasNewTarget = hasNewTarget) def withThisType(thisType: Type): Env = - copy(thisType = thisType) + withLocal(LocalDef(LocalName.This, thisType, mutable = false)) + + def withMaybeThisType(hasThis: Boolean, thisType: Type): Env = + if (hasThis) withThisType(thisType) + else this def withLocal(localDef: LocalDef): Env = copy(locals = locals + (localDef.name -> localDef)) @@ -1100,12 +1099,11 @@ object ClassDefChecker { private def copy( hasNewTarget: Boolean = hasNewTarget, - thisType: Type = thisType, locals: Map[LocalName, LocalDef] = locals, returnLabels: Set[LabelName] = returnLabels, isThisRestricted: Boolean = isThisRestricted ): Env = { - new Env(hasNewTarget, thisType, locals, returnLabels, isThisRestricted) + new Env(hasNewTarget, locals, returnLabels, isThisRestricted) } } @@ -1113,7 +1111,6 @@ object ClassDefChecker { val empty: Env = { new Env( hasNewTarget = false, - thisType = VoidType, locals = Map.empty, returnLabels = Set.empty, isThisRestricted = false @@ -1127,7 +1124,6 @@ object ClassDefChecker { new Env( hasNewTarget = false, - thisType = VoidType, paramLocalDefs.toMap, Set.empty, isThisRestricted = false diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/ErrorReporter.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/ErrorReporter.scala index 18c6527d13..ab45bd4316 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/ErrorReporter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/ErrorReporter.scala @@ -35,6 +35,8 @@ private[checker] object ErrorReporter { private def format(arg: Any): String = { arg match { + case arg: LocalName if arg.isThis => "`this`" + case arg: Name => arg.nameString case arg: FieldName => arg.nameString case arg: MethodName => arg.displayName diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala index 4c2c4f8464..a749e4807e 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala @@ -692,8 +692,6 @@ private final class IRChecker(unit: LinkingUnit, reporter: ErrorReporter, case _: VarRef => - case This() => - case Closure(arrow, captureParams, params, restParam, body, captureValues) => assert(captureParams.size == captureValues.size) // checked by ClassDefChecker diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala index a3fb5319c3..6d4aa83022 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala @@ -876,7 +876,7 @@ final class IncOptimizer private[optimizer] (config: CommonPhaseConfig, collOps: val getterDependenciesBuilder = Set.newBuilder[(ClassName, MethodName)] def isTriviallySideEffectFree(tree: Tree): Boolean = tree match { - case _:VarRef | _:Literal | _:This | _:Skip => + case _:VarRef | _:Literal | _:Skip => true case Closure(_, _, _, _, _, captureValues) => diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index d55b70744d..5f275ea841 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -232,16 +232,6 @@ private[optimizer] abstract class OptimizerCore( (newName, newOriginalName) } - private def freshLocalName(base: Binding.Name, - mutable: Boolean): (LocalName, OriginalName) = { - base match { - case Binding.This => - freshLocalName(LocalThisNameForFresh, thisOriginalName, mutable) - case Binding.Local(name, originalName) => - freshLocalName(name, originalName, mutable) - } - } - private def freshLabelName(base: LabelName): LabelName = labelNameAllocator.freshName(base) @@ -659,7 +649,7 @@ private[optimizer] abstract class OptimizerCore( // Atomic expressions - case _:VarRef | _:This => + case _:VarRef => trampoline { pretransformExpr(tree)(finishTransform(isStat)) } @@ -779,7 +769,8 @@ private[optimizer] abstract class OptimizerCore( case PreTransLit(literal) => captureParamLocalDefs += paramName -> LocalDef(tcaptureValue.tpe, false, ReplaceWithConstant(literal)) - case PreTransLocalDef(LocalDef(_, /* mutable = */ false, ReplaceWithVarRef(captureName, _))) => + case PreTransLocalDef(LocalDef(_, /* mutable = */ false, ReplaceWithVarRef(captureName, _))) + if !captureName.isThis => captureParamLocalDefsForVarRefs.get(captureName).fold[Unit] { captureParamLocalDefsForVarRefs += captureName -> addCaptureParam(captureName) } { prevLocalDef => @@ -906,16 +897,6 @@ private[optimizer] abstract class OptimizerCore( }) cont(localDef.toPreTransform) - case This() => - val localDef = scope.env.thisLocalDef.getOrElse { - throw new AssertionError( - s"Found invalid 'this' at $pos\n" + - s"While optimizing $debugID\n" + - s"Env is ${scope.env}\n" + - s"Inlining ${scope.implsBeingInlined}") - } - cont(localDef.toPreTransform) - case tree: If => pretransformIf(tree)(cont) @@ -1591,7 +1572,7 @@ private[optimizer] abstract class OptimizerCore( /** Keeps only the side effects of a Tree (overapproximation). */ private def keepOnlySideEffects(stat: Tree): Tree = stat match { - case _:VarRef | _:This | _:Literal | _:SelectStatic => + case _:VarRef | _:Literal | _:SelectStatic => Skip()(stat.pos) case VarDef(_, _, _, _, rhs) => keepOnlySideEffects(rhs) @@ -1903,9 +1884,6 @@ private[optimizer] abstract class OptimizerCore( case _: Literal => NotFoundPureSoFar - case This() => - NotFoundPureSoFar - case Closure(arrow, captureParams, params, restParam, body, captureValues) => recs(captureValues).mapOrKeepGoing(Closure(arrow, captureParams, params, restParam, body, _)) @@ -2105,12 +2083,11 @@ private[optimizer] abstract class OptimizerCore( * the unboxes in the correct evaluation order. */ - /* Generate a new, fake body that we will inline. For - * type-preservation, the type of its `This()` node is the type of - * our receiver but non-nullable. For stability, the parameter - * names are normalized (taking them from `body` would make the - * result depend on which method came up first in the list of - * targets). + /* Generate a new, fake body that we will inline. For type + * preservation, the type of its `this` var ref is the type of our + * receiver but non-nullable. For stability, the parameter names + * are normalized (taking them from `body` would make the result + * depend on which method came up first in the list of targets). */ val thisType = treceiver.tpe.base.toNonNullable val normalizedParams: List[(LocalName, Type)] = { @@ -2132,10 +2109,10 @@ private[optimizer] abstract class OptimizerCore( // Construct bindings; need to check null for the receiver to preserve evaluation order val receiverBinding = - Binding(Binding.This, thisType, mutable = false, checkNotNull(treceiver)) + Binding.forReceiver(thisType, checkNotNull(treceiver)) val argsBindings = normalizedParams.zip(targs).map { case ((name, ptpe), targ) => - Binding(Binding.Local(name, NoOriginalName), ptpe, mutable = false, targ) + Binding(name, NoOriginalName, ptpe, mutable = false, targ) } withBindings(receiverBinding :: argsBindings) { (bodyScope, cont1) => @@ -2581,12 +2558,12 @@ private[optimizer] abstract class OptimizerCore( case This() if args.isEmpty => assert(optReceiver.isDefined, - "There was a This(), there should be a receiver") + "There was a `this`, there should be a receiver") cont(foldCast(checkNotNull(optReceiver.get._2), optReceiver.get._1)) case Select(This(), field) if formals.isEmpty => assert(optReceiver.isDefined, - "There was a This(), there should be a receiver") + "There was a `this`, there should be a receiver") pretransformSelectCommon(body.tpe, optReceiver.get._2, optQualDeclaredType = Some(optReceiver.get._1), field, isLhsOfAssign = false)(cont) @@ -2595,7 +2572,7 @@ private[optimizer] abstract class OptimizerCore( if formals.size == 1 && formals.head.name.name == rhsName => assert(isStat, "Found Assign in expression position") assert(optReceiver.isDefined, - "There was a This(), there should be a receiver") + "There was a `this`, there should be a receiver") val treceiver = optReceiver.get._2 val trhs = args.head @@ -2634,7 +2611,7 @@ private[optimizer] abstract class OptimizerCore( */ val (declaredType, value0) = receiver val value = foldCast(checkNotNull(value0), declaredType) - Binding(Binding.This, declaredType, false, value) + Binding.forReceiver(declaredType, value) } assert(formals.size == args.size, @@ -3193,8 +3170,7 @@ private[optimizer] abstract class OptimizerCore( val initialFieldBindings = for { RecordType.Field(name, originalName, tpe, mutable) <- structure.recordType.fields } yield { - Binding(Binding.Local(name.toLocalName, originalName), tpe, mutable, - PreTransTree(zeroOf(tpe))) + Binding(name.toLocalName, originalName, tpe, mutable, PreTransTree(zeroOf(tpe))) } withNewLocalDefs(initialFieldBindings) { (initialFieldLocalDefList, cont1) => @@ -3282,22 +3258,22 @@ private[optimizer] abstract class OptimizerCore( } stats match { - case This() :: rest => + case VarRef(_) :: rest => + // mostly for `this` inlineClassConstructorBodyList(allocationSite, structure, thisLocalDef, inputFieldsLocalDefs, className, rest, cancelFun)(buildInner)(cont) - case Assign(s @ Select(ths: This, field), value) :: rest + case Assign(s @ Select(This(), field), value) :: rest if !inputFieldsLocalDefs.contains(field.name) => // Field is being optimized away. Only keep side effects of the write. withStat(value, rest) - case Assign(s @ Select(ths: This, field), value) :: rest + case Assign(s @ Select(This(), field), value) :: rest if !inputFieldsLocalDefs(field.name).mutable => pretransformExpr(value) { tvalue => val originalName = structure.fieldOriginalName(field.name) - val binding = Binding( - Binding.Local(field.name.simpleName.toLocalName, originalName), - s.tpe, false, tvalue) + val binding = Binding(field.name.simpleName.toLocalName, + originalName, s.tpe, mutable = false, tvalue) withNewLocalDef(binding) { (localDef, cont1) => if (localDef.contains(thisLocalDef)) { /* Uh oh, there is a `val x = ...this...`. We can't keep it, @@ -3342,7 +3318,7 @@ private[optimizer] abstract class OptimizerCore( Assign(lhs, If(cond, th, value)(lhs.tpe)(stat.pos))(ass.pos) :: rest, cancelFun)(buildInner)(cont) - case ApplyStatically(flags, ths: This, superClass, superCtor, args) :: rest + case ApplyStatically(flags, This(), superClass, superCtor, args) :: rest if flags.isConstructor => pretransformExprs(args) { targs => inlineClassConstructorBody(allocationSite, structure, @@ -5338,8 +5314,10 @@ private[optimizer] abstract class OptimizerCore( (name -> localDef, newParamDef) } - private def newThisLocalDef(thisType: Type): LocalDef = - LocalDef(RefinedType(thisType), false, ReplaceWithThis()) + private def newThisLocalDef(thisType: Type): LocalDef = { + LocalDef(RefinedType(thisType), false, + ReplaceWithVarRef(LocalName.This, new SimpleState(this, UsedAtLeastOnce))) + } private def withBindings(bindings: List[Binding])( buildInner: (Scope, PreTransCont) => TailRec[Tree])( @@ -5403,7 +5381,7 @@ private[optimizer] abstract class OptimizerCore( buildInner: (LocalDef, PreTransCont) => TailRec[Tree])( cont: PreTransCont)( implicit scope: Scope): TailRec[Tree] = tailcall { - val Binding(bindingName, declaredType, mutable, value) = binding + val Binding(bindingName, originalName, declaredType, mutable, value) = binding implicit val pos = value.pos def withDedicatedVar(tpe: RefinedType): TailRec[Tree] = { @@ -5423,13 +5401,13 @@ private[optimizer] abstract class OptimizerCore( * RuntimeLong. */ expandLongValue(value) { expandedValue => - val expandedBinding = Binding(bindingName, rtLongClassType, - mutable, expandedValue) + val expandedBinding = Binding(bindingName, originalName, + rtLongClassType, mutable, expandedValue) withNewLocalDef(expandedBinding)(buildInner)(cont) } } else { // Otherwise, we effectively declare a new binding - val (newName, newOriginalName) = freshLocalName(bindingName, mutable) + val (newName, newOriginalName) = freshLocalName(bindingName, originalName, mutable) val used = newSimpleState[IsUsed](Unused) @@ -5609,7 +5587,7 @@ private[optimizer] abstract class OptimizerCore( private[optimizer] object OptimizerCore { - /** When creating a `freshName` based on a `Binding.This`, use this name as + /** When creating a `freshName` based on a `LocalName.This`, use this name as * base. */ private val LocalThisNameForFresh = LocalName("this") @@ -5855,15 +5833,11 @@ private[optimizer] object OptimizerCore { case ReplaceWithRecordVarRef(_, _, _, cancelFun) => cancelFun() - case ReplaceWithThis() => - This()(tpe.base) - case ReplaceWithOtherLocalDef(localDef) => /* A previous version would push down the `tpe` of this `LocalDef` to * use for the replacement. While that creates trees with narrower types, - * it also creates inconsistent trees: - * - This() not typed as the enclosing class. - * - VarRef not typed as the corresponding VarDef / ParamDef. + * it also creates inconsistent trees, with `VarRef`s that are not typed + * as the corresponding VarDef / ParamDef / receiver type. * * Type based optimizations happen (mainly) in the optimizer so * consistent downstream types are more important than narrower types; @@ -5917,20 +5891,19 @@ private[optimizer] object OptimizerCore { elemLocalDefs.exists(_.contains(that)) case _:ReplaceWithVarRef | _:ReplaceWithRecordVarRef | - _:ReplaceWithThis | _:ReplaceWithConstant => + _:ReplaceWithConstant => false }) } def tryWithRefinedType(refinedType: RefinedType): LocalDef = { - /* Only adjust if the replacement if ReplaceWithThis or - * ReplaceWithVarRef, because other types have nothing to gain - * (e.g., ReplaceWithConstant) or we want to keep them unwrapped - * because they are examined in optimizations (notably all the - * types with virtualized objects). + /* Only adjust if the replacement if ReplaceWithVarRef, because other + * types have nothing to gain (e.g., ReplaceWithConstant) or we want to + * keep them unwrapped because they are examined in optimizations + * (notably all the types with virtualized objects). */ replacement match { - case _:ReplaceWithThis | _:ReplaceWithVarRef => + case _:ReplaceWithVarRef => LocalDef(refinedType, mutable, ReplaceWithOtherLocalDef(this)) case replacement: ReplaceWithOtherLocalDef => LocalDef(refinedType, mutable, replacement) @@ -5950,8 +5923,6 @@ private[optimizer] object OptimizerCore { used: SimpleState[IsUsed], cancelFun: CancelFun) extends LocalDefReplacement - private final case class ReplaceWithThis() extends LocalDefReplacement - /** An alias to another `LocalDef`, used only to refine the type of that * `LocalDef` in a specific scope. * @@ -6002,41 +5973,37 @@ private[optimizer] object OptimizerCore { val returnedStructures: SimpleState[List[InlineableClassStructure]]) private class OptEnv( - val thisLocalDef: Option[LocalDef], val localDefs: Map[LocalName, LocalDef], val labelInfos: Map[LabelName, LabelInfo]) { def withThisLocalDef(rep: LocalDef): OptEnv = - withThisLocalDef(Some(rep)) + withLocalDef(LocalName.This, rep) + /** Optionally adds a binding for `this`. + * + * If `rep.isEmpty`, returns this environment unchanged. + */ def withThisLocalDef(rep: Option[LocalDef]): OptEnv = - new OptEnv(rep, localDefs, labelInfos) + if (rep.isEmpty) this + else withThisLocalDef(rep.get) def withLocalDef(oldName: LocalName, rep: LocalDef): OptEnv = - new OptEnv(thisLocalDef, localDefs + (oldName -> rep), labelInfos) - - def withLocalDef(oldName: Binding.Name, rep: LocalDef): OptEnv = { - oldName match { - case Binding.This => withThisLocalDef(rep) - case Binding.Local(name, _) => withLocalDef(name, rep) - } - } + new OptEnv(localDefs + (oldName -> rep), labelInfos) def withLocalDefs(reps: List[(LocalName, LocalDef)]): OptEnv = - new OptEnv(thisLocalDef, localDefs ++ reps, labelInfos) + new OptEnv(localDefs ++ reps, labelInfos) def withLabelInfo(oldName: LabelName, info: LabelInfo): OptEnv = - new OptEnv(thisLocalDef, localDefs, labelInfos + (oldName -> info)) + new OptEnv(localDefs, labelInfos + (oldName -> info)) override def toString(): String = { - "thisLocalDef:\n " + thisLocalDef.fold("")(_.toString()) + "\n" + "localDefs:" + localDefs.mkString("\n ", "\n ", "\n") + "labelInfos:" + labelInfos.mkString("\n ", "\n ", "") } } private object OptEnv { - val Empty: OptEnv = new OptEnv(None, Map.empty, Map.empty) + val Empty: OptEnv = new OptEnv(Map.empty, Map.empty) } private class Scope private ( @@ -6337,26 +6304,21 @@ private[optimizer] object OptimizerCore { } } - private final case class Binding(name: Binding.Name, declaredType: Type, - mutable: Boolean, value: PreTransform) + private final case class Binding(name: LocalName, originalName: OriginalName, + declaredType: Type, mutable: Boolean, value: PreTransform) private object Binding { - sealed abstract class Name - - case object This extends Name - - final case class Local(name: LocalName, originalName: OriginalName) - extends Name - def apply(localIdent: LocalIdent, originalName: OriginalName, declaredType: Type, mutable: Boolean, value: PreTransform): Binding = { - apply(Local(localIdent.name, originalName), declaredType, - mutable, value) + apply(localIdent.name, originalName, declaredType, mutable, value) } + def forReceiver(declaredType: Type, value: PreTransform): Binding = + apply(LocalName.This, NoOriginalName, declaredType, mutable = false, value) + def temp(baseName: LocalName, declaredType: Type, mutable: Boolean, value: PreTransform): Binding = { - apply(Local(baseName, NoOriginalName), declaredType, mutable, value) + apply(baseName, NoOriginalName, declaredType, mutable, value) } def temp(baseName: LocalName, value: PreTransform): Binding = @@ -6764,7 +6726,7 @@ private[optimizer] object OptimizerCore { val shouldInline = inlineable && { optimizerHints.inline || isForwarder || { body match { - case _:Skip | _:This | _:Literal => + case _:Skip | _:VarRef | _:Literal => true // Shape of accessors @@ -6852,7 +6814,7 @@ private[optimizer] object OptimizerCore { private val TraitInitSimpleMethodName = SimpleMethodName("$init$") private def isTrivialConstructorStat(stat: Tree): Boolean = stat match { - case This() => + case _: VarRef => true case ApplyStatically(_, This(), _, _, Nil) => true @@ -6915,7 +6877,7 @@ private[optimizer] object OptimizerCore { } private def isTrivialArg(arg: Tree): Boolean = arg match { - case _:VarRef | _:This | _:Literal | _:LoadModule => + case _:VarRef | _:Literal | _:LoadModule => true case _ => false @@ -7025,6 +6987,9 @@ private[optimizer] object OptimizerCore { EmitterReservedJSIdentifiers.map(i => LocalName(i) -> 1).toMap final class Local extends FreshNameAllocator[LocalName](InitialLocalMap) { + override def freshName(base: LocalName): LocalName = + super.freshName(if (base.isThis) LocalThisNameForFresh else base) + protected def nameWithSuffix(name: LocalName, suffix: String): LocalName = name.withSuffix(suffix) } @@ -7052,6 +7017,7 @@ private[optimizer] object OptimizerCore { def originalNameForFresh(base: Name, originalName: OriginalName, freshName: Name): OriginalName = { if (originalName.isDefined || (freshName eq base)) originalName + else if (base eq LocalName.This) thisOriginalName else OriginalName(base) } diff --git a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala index 0fe3988e97..d283ae189e 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala @@ -370,7 +370,7 @@ class IRCheckerTest { for { log <- testLinkIRErrors(classDefs, MainTestModuleInitializers, postOptimizer = true) } yield { - log.assertContainsError("Foo expected but Bar! found for tree of type org.scalajs.ir.Trees$This") + log.assertContainsError("Foo expected but Bar! found for tree of type org.scalajs.ir.Trees$VarRef") } } diff --git a/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala index 78e4e1dd2c..2d22331cc0 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/checker/ClassDefCheckerTest.scala @@ -383,6 +383,108 @@ class ClassDefCheckerTest { "Illegal constructor call") } + @Test + def illegalThisDeclarations(): Unit = { + val thisParamDef = paramDef(LocalName.This, AnyType) + + // Local var + assertError( + mainTestClassDef(Block( + VarDef(LocalName.This, NON, IntType, mutable = false, int(5)) + )), + "Illegal definition of a variable with name `this`" + ) + + // Method param + assertError( + classDef( + "Foo", superClass = Some(ObjectClass), + methods = List( + trivialCtor("Foo"), + MethodDef(EMF, m("foo", List(I), V), NON, List(thisParamDef), VoidType, Some(Skip()))(EOH, UNV) + ) + ), + "Illegal definition of a variable with name `this`" + ) + + // Capture param of a Closure + assertError( + mainTestClassDef(Block( + Closure(arrow = true, List(thisParamDef), Nil, None, int(5), List(int(6))) + )), + "Illegal definition of a variable with name `this`" + ) + + // Param of a closure + assertError( + mainTestClassDef(Block( + Closure(arrow = true, Nil, List(thisParamDef), None, int(5), Nil) + )), + "Illegal definition of a variable with name `this`" + ) + + // Rest param of a closure + assertError( + mainTestClassDef(Block( + Closure(arrow = true, Nil, Nil, Some(thisParamDef), int(5), Nil) + )), + "Illegal definition of a variable with name `this`" + ) + + // JS method param + assertError( + classDef( + "Foo", superClass = Some(ObjectClass), + kind = ClassKind.JSClass, + jsConstructor = Some(trivialJSCtor()), + jsMethodProps = List( + JSMethodDef(EMF, str("foo"), List(thisParamDef), None, Skip())(EOH, UNV) + ) + ), + "Illegal definition of a variable with name `this`" + ) + + // JS class capture + assertError( + classDef( + "Foo", superClass = Some(ObjectClass), + kind = ClassKind.JSClass, + jsClassCaptures = Some(List(thisParamDef)), + jsConstructor = Some(trivialJSCtor()) + ), + "Illegal JS class capture with name '`this`'" + ) + } + + @Test + def assignmentToImmutable(): Unit = { + assertError( + mainTestClassDef(Block( + VarDef("x", NON, IntType, mutable = false, int(5)), + Assign(VarRef("x")(IntType), int(6)) + )), + "Assignment to immutable variable x." + ) + + assertError( + classDef( + "Foo", superClass = Some(ObjectClass), + methods = List( + trivialCtor("Foo"), + MethodDef(EMF, m("foo", Nil, V), NON, Nil, VoidType, Some(Block( + Assign(thisFor("Foo"), thisFor("Foo")) + )))(EOH, UNV) + ) + ), + "Assignment to immutable variable `this`." + ) + + assertError( + mainTestClassDef(Assign(JSGlobalRef(JSGlobalRef.FileLevelThis), int(5))), + "Assignment to global this." + ) + } + @Test def thisType(): Unit = { def testThisTypeError(static: Boolean, expr: Tree, expectedMsg: String): Unit = { @@ -404,47 +506,47 @@ class ClassDefCheckerTest { testThisTypeError(static = true, This()(VoidType), - "Cannot find `this` in scope") + "Cannot find variable `this` in scope") testThisTypeError(static = true, This()(ClassType("Foo", nullable = false)), - "Cannot find `this` in scope") + "Cannot find variable `this` in scope") testThisTypeError(static = false, This()(VoidType), - "`this` of type Foo! typed as void") + "Variable `this` of type Foo! typed as void") testThisTypeError(static = false, This()(AnyType), - "`this` of type Foo! typed as any") + "Variable `this` of type Foo! typed as any") testThisTypeError(static = false, This()(AnyNotNullType), - "`this` of type Foo! typed as any!") + "Variable `this` of type Foo! typed as any!") testThisTypeError(static = false, This()(ClassType("Bar", nullable = false)), - "`this` of type Foo! typed as Bar!") + "Variable `this` of type Foo! typed as Bar!") testThisTypeError(static = false, This()(ClassType("Foo", nullable = true)), - "`this` of type Foo! typed as Foo") + "Variable `this` of type Foo! typed as Foo") testThisTypeError(static = false, Closure(arrow = true, Nil, Nil, None, This()(VoidType), Nil), - "Cannot find `this` in scope") + "Cannot find variable `this` in scope") testThisTypeError(static = false, Closure(arrow = true, Nil, Nil, None, This()(AnyType), Nil), - "Cannot find `this` in scope") + "Cannot find variable `this` in scope") testThisTypeError(static = false, Closure(arrow = false, Nil, Nil, None, This()(VoidType), Nil), - "`this` of type any typed as void") + "Variable `this` of type any typed as void") testThisTypeError(static = false, Closure(arrow = false, Nil, Nil, None, This()(ClassType("Foo", nullable = false)), Nil), - "`this` of type any typed as Foo!") + "Variable `this` of type any typed as Foo!") } @Test diff --git a/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala b/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala index d48649c245..e6b045c286 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala @@ -167,7 +167,7 @@ object TestIRBuilder { else None } - def thisFor(cls: ClassName): This = + def thisFor(cls: ClassName): VarRef = This()(ClassType(cls, nullable = false)) implicit def string2LocalName(name: String): LocalName = diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index 305e97d428..5df88fa444 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -21,6 +21,10 @@ object BinaryIncompatibilities { ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$LabelIdent$"), ProblemFilters.exclude[Problem]("org.scalajs.ir.Trees#Labeled.*"), ProblemFilters.exclude[Problem]("org.scalajs.ir.Trees#Return.*"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$This"), + ProblemFilters.exclude[MissingTypesProblem]("org.scalajs.ir.Trees$This$"), + ProblemFilters.exclude[IncompatibleResultTypeProblem]("org.scalajs.ir.Trees#This.apply"), + ProblemFilters.exclude[IncompatibleMethTypeProblem]("org.scalajs.ir.Trees#This.unapply"), ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$Throw"), ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$Throw$"), ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Trees$UnwrapFromThrowable"),