Skip to content

Commit 721b9f9

Browse files
committed
Give constructor methods a result type of NoType.
Previously, constructor methods explicitly returned `this`, and advertised it in the result type of their signature. This was an implementation detail of the encoding in JS, for which we want init methods to return `this` so that we can chain them with `new`. This commit fixes the specification of init methods with NoType as result type. They do not explicitly return `this` at the IR level anymore. It is the responsibility of the desugaring to JavaScript to introduce the `return this`. This commit does not change the compiler yet, so that we can test the backward binary compatibility hack.
1 parent 20c65d6 commit 721b9f9

File tree

6 files changed

+75
-32
lines changed

6 files changed

+75
-32
lines changed

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

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import java.net.URI
1616

1717
import scala.collection.mutable
1818

19+
import Definitions.isConstructorName
1920
import Position._
2021
import Trees._
2122
import Types._
@@ -587,6 +588,8 @@ object Serializers {
587588

588589
private final class Deserializer(stream: InputStream, sourceVersion: String) {
589590
private[this] val useHacks060 = sourceVersion == "0.6.0"
591+
private[this] val useHacks065 = true
592+
//Set("0.6.0", "0.6.3", "0.6.4", "0.6.5").contains(sourceVersion)
590593

591594
private[this] val input = new DataInputStream(stream)
592595

@@ -638,7 +641,13 @@ object Serializers {
638641
case TagStoreModule => StoreModule(readClassType(), readTree())
639642
case TagSelect => Select(readTree(), readIdent())(readType())
640643
case TagApply => Apply(readTree(), readIdent(), readTrees())(readType())
641-
case TagApplyStatically => ApplyStatically(readTree(), readClassType(), readIdent(), readTrees())(readType())
644+
case TagApplyStatically =>
645+
val result1 =
646+
ApplyStatically(readTree(), readClassType(), readIdent(), readTrees())(readType())
647+
if (useHacks065 && result1.tpe != NoType && isConstructorName(result1.method.name))
648+
result1.copy()(NoType)
649+
else
650+
result1
642651
case TagApplyStatic => ApplyStatic(readClassType(), readIdent(), readTrees())(readType())
643652
case TagUnaryOp => UnaryOp(readByte(), readTree())
644653
case TagBinaryOp => BinaryOp(readByte(), readTree(), readTree())
@@ -720,14 +729,22 @@ object Serializers {
720729
// read and discard the length
721730
val len = readInt()
722731
assert(len >= 0)
723-
val result = MethodDef(readBoolean(), readPropertyName(),
732+
val result1 = MethodDef(readBoolean(), readPropertyName(),
724733
readParamDefs(), readType(), readTree())(
725734
new OptimizerHints(readInt()), optHash)
726-
if (foundArguments) {
735+
val result2 = if (foundArguments) {
727736
foundArguments = false
728-
new RewriteArgumentsTransformer().transformMethodDef(result)
737+
new RewriteArgumentsTransformer().transformMethodDef(result1)
729738
} else {
730-
result
739+
result1
740+
}
741+
if (useHacks065 && result2.resultType != NoType &&
742+
isConstructorName(result2.name.name)) {
743+
result2.copy(resultType = NoType, body = result2.body)(
744+
result2.optimizerHints, result2.hash)(
745+
result2.pos)
746+
} else {
747+
result2
731748
}
732749
case TagPropertyDef =>
733750
PropertyDef(readPropertyName(), readTree(),

test-suite/js/src/test/resources/SourceMapTestTemplate.scala

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,23 @@ object SourceMapTest extends JasmineTest {
5353
val trace2 = trace1.dropWhile(
5454
normFileName(_).endsWith("/java/lang/Throwables.scala"))
5555

56-
val exSte :: throwSte :: _ = trace2
57-
58-
expect(normFileName(exSte)).toContain("/SourceMapTest.scala")
59-
// line where `case class TestException is written` above
60-
expect(exSte.getLineNumber).toBe(15)
56+
val topSte = trace2.head
57+
expect(normFileName(topSte)).toContain("/SourceMapTest.scala")
58+
59+
val throwSte = if (topSte.getLineNumber == 15) {
60+
// line where `case class TestException is written` above
61+
val throwSte = trace2.tail.head
62+
expect(normFileName(throwSte)).toContain("/SourceMapTest.scala")
63+
throwSte
64+
} else {
65+
/* In fullOpt, it may happen that the constructor of
66+
* TestException is inlined, in which case there is no trace of
67+
* it anymore. The first stack element in SourceMapTest.scala is
68+
* therefore the one we're interested in.
69+
*/
70+
topSte
71+
}
6172

62-
expect(normFileName(throwSte)).toContain("/SourceMapTest.scala")
6373
expect(throwSte.getLineNumber).toBe(lineNo)
6474
}
6575
}

tools/shared/src/main/scala/org/scalajs/core/tools/javascript/ScalaJSClassEmitter.scala

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,9 +320,21 @@ final class ScalaJSClassEmitter private (
320320
def genMethod(className: String, method: MethodDef): js.Tree = {
321321
implicit val pos = method.pos
322322

323-
val methodFun = desugarToFunction(this, className,
323+
val methodFun0 = desugarToFunction(this, className,
324324
method.args, method.body, method.resultType == NoType)
325325

326+
val methodFun = if (Definitions.isConstructorName(method.name.name)) {
327+
// init methods have to return `this` so that we can chain them to `new`
328+
js.Function(methodFun0.args, {
329+
implicit val pos = methodFun0.body.pos
330+
js.Block(
331+
methodFun0.body,
332+
js.Return(js.This()))
333+
})(methodFun0.pos)
334+
} else {
335+
methodFun0
336+
}
337+
326338
outputMode match {
327339
case OutputMode.ECMAScript6StrongMode =>
328340
js.MethodDef(static = method.static, genPropertyName(method.name),

tools/shared/src/main/scala/org/scalajs/core/tools/optimizer/GenIncOptimizer.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -569,8 +569,8 @@ abstract class GenIncOptimizer(semantics: Semantics, outputMode: OutputMode,
569569
/** UPDATE PASS ONLY. */
570570
private def isElidableModuleConstructor(impl: MethodImpl): Boolean = {
571571
def isTriviallySideEffectFree(tree: Tree): Boolean = tree match {
572-
case _:VarRef | _:Literal | _:This => true
573-
case _ => false
572+
case _:VarRef | _:Literal | _:This | _:Skip => true
573+
case _ => false
574574
}
575575
def isElidableStat(tree: Tree): Boolean = tree match {
576576
case Block(stats) =>

tools/shared/src/main/scala/org/scalajs/core/tools/optimizer/IRChecker.scala

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -186,11 +186,8 @@ class IRChecker(unit: LinkingUnit, logger: Logger) {
186186
}
187187

188188
val isConstructor = isConstructorName(name)
189-
val resultTypeForSig =
190-
if (isConstructor) NoType
191-
else resultType
192189

193-
val advertizedSig = (params.map(_.ptpe), resultTypeForSig)
190+
val advertizedSig = (params.map(_.ptpe), resultType)
194191
val sigFromName = inferMethodType(name, static)
195192
if (advertizedSig != sigFromName) {
196193
reportError(
@@ -527,15 +524,15 @@ class IRChecker(unit: LinkingUnit, logger: Logger) {
527524
implicit val ctx = ErrorContext(tree)
528525

529526
def checkApplyGeneric(methodName: String, methodFullName: String,
530-
args: List[Tree], isStatic: Boolean): Unit = {
527+
args: List[Tree], tpe: Type, isStatic: Boolean): Unit = {
531528
val (methodParams, resultType) = inferMethodType(methodName, isStatic)
532529
if (args.size != methodParams.size)
533530
reportError(s"Arity mismatch: ${methodParams.size} expected but "+
534531
s"${args.size} found")
535532
for ((actual, formal) <- args zip methodParams) {
536533
typecheckExpect(actual, env, formal)
537534
}
538-
if (!isConstructorName(methodName) && tree.tpe != resultType)
535+
if (tpe != resultType)
539536
reportError(s"Call to $methodFullName of type $resultType "+
540537
s"typed as ${tree.tpe}")
541538
}
@@ -607,7 +604,8 @@ class IRChecker(unit: LinkingUnit, logger: Logger) {
607604
val clazz = lookupClass(cls)
608605
if (!clazz.kind.isClass)
609606
reportError(s"new $cls which is not a class")
610-
checkApplyGeneric(ctor.name, s"$cls.$ctor", args, isStatic = false)
607+
checkApplyGeneric(ctor.name, s"$cls.$ctor", args, NoType,
608+
isStatic = false)
611609

612610
case LoadModule(cls) =>
613611
if (!cls.className.endsWith("$"))
@@ -640,16 +638,18 @@ class IRChecker(unit: LinkingUnit, logger: Logger) {
640638

641639
case Apply(receiver, Ident(method, _), args) =>
642640
val receiverType = typecheckExpr(receiver, env)
643-
checkApplyGeneric(method, s"$receiverType.$method", args,
641+
checkApplyGeneric(method, s"$receiverType.$method", args, tree.tpe,
644642
isStatic = false)
645643

646644
case ApplyStatically(receiver, cls, Ident(method, _), args) =>
647645
typecheckExpect(receiver, env, cls)
648-
checkApplyGeneric(method, s"$cls.$method", args, isStatic = false)
646+
checkApplyGeneric(method, s"$cls.$method", args, tree.tpe,
647+
isStatic = false)
649648

650649
case ApplyStatic(cls, Ident(method, _), args) =>
651650
val clazz = lookupClass(cls)
652-
checkApplyGeneric(method, s"$cls.$method", args, isStatic = true)
651+
checkApplyGeneric(method, s"$cls.$method", args, tree.tpe,
652+
isStatic = true)
653653

654654
case UnaryOp(op, lhs) =>
655655
import UnaryOp._

tools/shared/src/main/scala/org/scalajs/core/tools/optimizer/OptimizerCore.scala

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1759,8 +1759,12 @@ private[optimizer] abstract class OptimizerCore(
17591759
if (scope.implsBeingInlined.contains(targetID))
17601760
cancelFun()
17611761

1762-
val MethodDef(_, _, formals, _, BlockOrAlone(stats, This())) =
1763-
getMethodBody(target)
1762+
val targetMethodDef = getMethodBody(target)
1763+
val formals = targetMethodDef.args
1764+
val stats = targetMethodDef.body match {
1765+
case Block(stats) => stats
1766+
case singleStat => List(singleStat)
1767+
}
17641768

17651769
val argsBindings = for {
17661770
(ParamDef(Ident(name, originalName), tpe, mutable, _), arg) <- formals zip args
@@ -2170,7 +2174,7 @@ private[optimizer] abstract class OptimizerCore(
21702174
case FloatLiteral(value) =>
21712175
foldToStringForString_+(DoubleLiteral(value.toDouble))
21722176

2173-
case DoubleLiteral(value) =>
2177+
case DoubleLiteral(value) =>
21742178
jsNumberToString(value).fold(tree)(StringLiteral(_))
21752179

21762180
case LongLiteral(value) => StringLiteral(value.toString)
@@ -2183,9 +2187,9 @@ private[optimizer] abstract class OptimizerCore(
21832187

21842188
/* Following the ECMAScript 6 specification */
21852189
private def jsNumberToString(value: Double): Option[String] = {
2186-
if (1.0.toString == "1") {
2190+
if (1.0.toString == "1") {
21872191
// We are in a JS environment, so the host .toString() is the correct one.
2188-
Some(value.toString)
2192+
Some(value.toString)
21892193
} else {
21902194
value match {
21912195
case _ if value.isNaN => Some("NaN")
@@ -2195,7 +2199,7 @@ private[optimizer] abstract class OptimizerCore(
21952199
case _ if value.isValidInt => Some(value.toInt.toString)
21962200
case _ => None
21972201
}
2198-
}
2202+
}
21992203
}
22002204

22012205
private def foldBinaryOp(op: BinaryOp.Code, lhs: Tree, rhs: Tree)(
@@ -2218,13 +2222,13 @@ private[optimizer] abstract class OptimizerCore(
22182222
val rhs1 = foldToStringForString_+(rhs)
22192223
@inline def stringDefault = BinaryOp(String_+, lhs1, rhs1)
22202224
(lhs1, rhs1) match {
2221-
case (StringLiteral(s1), StringLiteral(s2)) =>
2225+
case (StringLiteral(s1), StringLiteral(s2)) =>
22222226
StringLiteral(s1 + s2)
22232227
case (_, StringLiteral("")) =>
22242228
foldBinaryOp(op, rhs1, lhs1)
22252229
case (StringLiteral(""), _) if rhs1.tpe == StringType =>
22262230
rhs1
2227-
case (_, BinaryOp(String_+, rl, rr)) =>
2231+
case (_, BinaryOp(String_+, rl, rr)) =>
22282232
foldBinaryOp(String_+, BinaryOp(String_+, lhs1, rl), rr)
22292233
case (BinaryOp(String_+, ll, StringLiteral(lr)), StringLiteral(r)) =>
22302234
BinaryOp(String_+, ll, StringLiteral(lr + r))

0 commit comments

Comments
 (0)