Skip to content

Commit b2a66ac

Browse files
authored
Merge pull request #10564 from dotty-staging/sjs-fix-static-methods-in-jsclass
Fix #10563: Handle static methods in non-native JS classes.
2 parents 77e2165 + 8d40763 commit b2a66ac

File tree

4 files changed

+28
-13
lines changed

4 files changed

+28
-13
lines changed

compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1122,8 +1122,9 @@ class JSCodeGen()(using genCtx: Context) {
11221122
* type is Unit, then the body is emitted as a statement. Otherwise, it is
11231123
* emitted as an expression.
11241124
*
1125-
* Methods Scala.js-defined JS classes are compiled as static methods taking
1126-
* an explicit parameter for their `this` value.
1125+
* Instance methods in non-native JS classes are compiled as static methods
1126+
* taking an explicit parameter for their `this` value. Static methods in
1127+
* non-native JS classes are compiled as is, like methods in Scala classes.
11271128
*/
11281129
private def genMethodDef(namespace: js.MemberNamespace, methodName: js.MethodIdent,
11291130
originalName: OriginalName, paramsSyms: List[Symbol], resultIRType: jstpe.Type,
@@ -1137,13 +1138,11 @@ class JSCodeGen()(using genCtx: Context) {
11371138
else genExpr(tree)
11381139
}
11391140

1140-
if (!currentClassSym.isNonNativeJSClass) {
1141+
if (namespace.isStatic || !currentClassSym.isNonNativeJSClass) {
11411142
val flags = js.MemberFlags.empty.withNamespace(namespace)
11421143
js.MethodDef(flags, methodName, originalName, jsParams, resultIRType, Some(genBody()))(
11431144
optimizerHints, None)
11441145
} else {
1145-
assert(!namespace.isStatic, tree.span)
1146-
11471146
val thisLocalIdent = freshLocalIdent("this")
11481147
withScopedVars(
11491148
thisLocalVarIdent := Some(thisLocalIdent)

compiler/src/dotty/tools/backend/sjs/JSEncoding.scala

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import NameOps._
1919
import Names._
2020
import StdNames._
2121

22+
import dotty.tools.dotc.transform.sjs.JSSymUtils._
23+
2224
import org.scalajs.ir
2325
import org.scalajs.ir.{Trees => js, Types => jstpe}
2426
import org.scalajs.ir.Names.{LocalName, LabelName, FieldName, SimpleMethodName, MethodName, ClassName}
@@ -28,7 +30,6 @@ import org.scalajs.ir.UTF8String
2830

2931
import ScopedVar.withScopedVars
3032
import JSDefinitions._
31-
import JSInterop._
3233

3334
import dotty.tools.backend.jvm.DottyBackendInterface.symExtensions
3435

@@ -199,7 +200,7 @@ object JSEncoding {
199200

200201
val paramTypeRefs0 = tpe.firstParamTypes.map(paramOrResultTypeRef(_))
201202

202-
val hasExplicitThisParameter = isScalaJSDefinedJSClass(sym.owner)
203+
val hasExplicitThisParameter = !sym.is(JavaStatic) && sym.owner.isNonNativeJSClass
203204
val paramTypeRefs =
204205
if (!hasExplicitThisParameter) paramTypeRefs0
205206
else encodeClassRef(sym.owner) :: paramTypeRefs0
@@ -243,7 +244,7 @@ object JSEncoding {
243244

244245
def encodeClassType(sym: Symbol)(using Context): jstpe.Type = {
245246
if (sym == defn.ObjectClass) jstpe.AnyType
246-
else if (isJSType(sym)) jstpe.AnyType
247+
else if (sym.isJSType) jstpe.AnyType
247248
else {
248249
assert(sym != defn.ArrayClass,
249250
"encodeClassType() cannot be called with ArrayClass")
@@ -303,7 +304,7 @@ object JSEncoding {
303304

304305
case typeRef: jstpe.ClassRef =>
305306
val sym = typeRefInternal._2
306-
if (sym == defn.ObjectClass || isJSType(sym))
307+
if (sym == defn.ObjectClass || sym.isJSType)
307308
jstpe.AnyType
308309
else if (sym == defn.NothingClass)
309310
jstpe.NothingType

compiler/src/dotty/tools/backend/sjs/JSInterop.scala

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,6 @@ object JSInterop {
2222
def isJSType(sym: Symbol)(using Context): Boolean =
2323
sym.isJSType
2424

25-
/** Is this symbol a Scala.js-defined JS class, i.e., a non-native JS class? */
26-
def isScalaJSDefinedJSClass(sym: Symbol)(using Context): Boolean =
27-
sym.isNonNativeJSClass
28-
2925
/** Should this symbol be translated into a JS getter?
3026
*
3127
* This is true for any parameterless method, i.e., defined without `()`.

tests/sjs-junit/test/org/scalajs/testsuite/compiler/RegressionTestScala3.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,24 @@ package org.scalajs.testsuite.compiler
33
import org.junit.Assert._
44
import org.junit.Test
55

6+
import scala.scalajs.js
7+
68
class RegressionTestScala3 {
79
import RegressionTestScala3._
810

911
@Test def testRegressionDoubleDefinitionOfOuterPointerIssue10177(): Unit = {
1012
assertEquals(6, new OuterClassIssue10177().foo(5))
1113
}
14+
15+
@Test def testRegressionJSClassWithSyntheticStaticMethodsIssue10563(): Unit = {
16+
val obj1 = new JSClassWithSyntheticStaticMethodsIssue10563(Some(3))
17+
assertEquals(3, obj1.y)
18+
assertEquals(8, obj1.foo(5))
19+
20+
val obj2 = new JSClassWithSyntheticStaticMethodsIssue10563(None)
21+
assertEquals(-1, obj2.y)
22+
assertEquals(4, obj2.foo(5))
23+
}
1224
}
1325

1426
object RegressionTestScala3 {
@@ -22,3 +34,10 @@ object RegressionTestScala3 {
2234
def foo(x: Int): Int = new ChildClass().concreteMethod(x)
2335
}
2436
}
37+
38+
// This class needs to be at the top-level, not in an object, to reproduce the issue
39+
class JSClassWithSyntheticStaticMethodsIssue10563(x: Option[Int]) extends js.Object {
40+
val y: Int = x.getOrElse(-1) // lambda in constructor
41+
42+
def foo(z: Int): Int = x.getOrElse(-1) + z // lambda in method
43+
}

0 commit comments

Comments
 (0)