From e181bfca4a0c9ad562ebe1beb80fd9dcbe005ed0 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Tue, 6 Oct 2020 16:41:35 +0200 Subject: [PATCH 1/6] enable static fields on classes --- .../dotty/tools/backend/sjs/JSCodeGen.scala | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala index 8d31916b7401..e32e2ddaabfc 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala @@ -773,7 +773,7 @@ class JSCodeGen()(using genCtx: Context) { // Generate the fields of a class ------------------------------------------ /** Gen definitions for the fields of a class. */ - private def genClassFields(td: TypeDef): List[js.AnyFieldDef] = { + private def genClassFields(td: TypeDef): List[js.MemberDef] = { val classSym = td.symbol.asClass assert(currentClassSym.get == classSym, "genClassFields called with a ClassDef other than the current one") @@ -785,19 +785,37 @@ class JSCodeGen()(using genCtx: Context) { !f.isOneOf(Method | Module) && f.isTerm && !f.hasAnnotation(jsdefn.JSNativeAnnot) && !f.hasAnnotation(jsdefn.JSOptionalAnnot) - }.map({ f => + }.flatMap({ f => implicit val pos = f.span - val flags = js.MemberFlags.empty.withMutable(f.is(Mutable)) + val isStaticField = f.is(JavaStatic).ensuring(isStatic => !(isStatic && isJSClass)) + + val namespace = if isStaticField then js.MemberNamespace.PublicStatic else js.MemberNamespace.Public + val mutable = isStaticField || f.is(Mutable) + + val flags = js.MemberFlags.empty.withMutable(mutable).withNamespace(namespace) val irTpe = if (isJSClass) genExposedFieldIRType(f) else toIRType(f.info) if (isJSClass && f.isJSExposed) - js.JSFieldDef(flags, genExpr(f.jsName)(f.sourcePos), irTpe) + js.JSFieldDef(flags, genExpr(f.jsName)(f.sourcePos), irTpe) :: Nil else - js.FieldDef(flags, encodeFieldSym(f), originalNameOfField(f), irTpe) + val fieldDef = js.FieldDef(flags, encodeFieldSym(f), originalNameOfField(f), irTpe) + val rest = + if isStaticField then + val className = encodeClassName(classSym) + val body = js.Block( + js.LoadModule(className), + js.SelectStatic(className, encodeFieldSym(f))(toIRType(f.info))) + js.MethodDef(js.MemberFlags.empty.withNamespace(js.MemberNamespace.PublicStatic), + encodeStaticMemberSym(f), originalNameOfField(f), Nil, toIRType(f.info), + Some(body))( + OptimizerHints.empty, None) :: Nil + else + Nil + fieldDef :: rest }).toList } @@ -1433,8 +1451,6 @@ class JSCodeGen()(using genCtx: Context) { case Assign(lhs0, rhs) => val sym = lhs0.symbol - if (sym.is(JavaStaticTerm)) - throw new FatalError(s"Assignment to static member ${sym.fullName} not supported") def genRhs = genExpr(rhs) val lhs = lhs0 match { case lhs: Ident => desugarIdent(lhs).getOrElse(lhs) @@ -3899,8 +3915,13 @@ class JSCodeGen()(using genCtx: Context) { (f, true) } else*/ { - val f = js.Select(qual, encodeClassName(sym.owner), - encodeFieldSym(sym))(toIRType(sym.info)) + val f = + if sym.is(JavaStatic) then + js.SelectStatic(encodeClassName(sym.owner), + encodeFieldSym(sym))(toIRType(sym.info)) + else + js.Select(qual, encodeClassName(sym.owner), + encodeFieldSym(sym))(toIRType(sym.info)) (f, false) } From 240f618e5762339342bb10a76406fd8034e82d02 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Tue, 6 Oct 2020 16:42:38 +0200 Subject: [PATCH 2/6] fix #9809: when compiling scala.js - enum value forwarders are defs --- .../tools/dotc/transform/CompleteJavaEnums.scala | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala b/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala index d308cea9f6d7..cf6f2788042a 100644 --- a/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala +++ b/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala @@ -98,16 +98,21 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase => /** Return a list of forwarders for enum values defined in the companion object * for java interop. */ - private def addedEnumForwarders(clazz: Symbol)(using Context): List[ValDef] = { + private def addedEnumForwarders(clazz: Symbol)(using Context): List[MemberDef] = { val moduleCls = clazz.companionClass val moduleRef = ref(clazz.companionModule) val enums = moduleCls.info.decls.filter(member => member.isAllOf(EnumValue)) for { enumValue <- enums } yield { - val fieldSym = newSymbol(clazz, enumValue.name.asTermName, EnumValue | JavaStatic, enumValue.info) - fieldSym.addAnnotation(Annotations.Annotation(defn.ScalaStaticAnnot)) - ValDef(fieldSym, moduleRef.select(enumValue)) + if ctx.settings.scalajs.value then + val methodSym = newSymbol(clazz, enumValue.name.asTermName, EnumValue | Method | JavaStatic, MethodType(Nil, enumValue.info)) + methodSym.addAnnotation(Annotations.Annotation(defn.ScalaStaticAnnot)) + DefDef(methodSym, moduleRef.select(enumValue)) + else + val fieldSym = newSymbol(clazz, enumValue.name.asTermName, EnumValue | JavaStatic, enumValue.info) + fieldSym.addAnnotation(Annotations.Annotation(defn.ScalaStaticAnnot)) + ValDef(fieldSym, moduleRef.select(enumValue)) } } From f0395c8452d746f6e61b14135d5cff61a2e08ff4 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Tue, 6 Oct 2020 17:40:16 +0200 Subject: [PATCH 3/6] add tests for enums compiled with Scala.js --- tests/neg-scalajs/js-enums.check | 24 ++++ tests/neg-scalajs/js-enums.scala | 18 +++ .../testsuite/compiler/EnumTestScala3.scala | 123 ++++++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 tests/neg-scalajs/js-enums.check create mode 100644 tests/neg-scalajs/js-enums.scala create mode 100644 tests/sjs-junit/test/org/scalajs/testsuite/compiler/EnumTestScala3.scala diff --git a/tests/neg-scalajs/js-enums.check b/tests/neg-scalajs/js-enums.check new file mode 100644 index 000000000000..eb358f16e57a --- /dev/null +++ b/tests/neg-scalajs/js-enums.check @@ -0,0 +1,24 @@ +-- Error: tests/neg-scalajs/js-enums.scala:4:5 ------------------------------------------------------------------------- +4 |enum MyEnum extends js.Object: // error + |^ + |MyEnum extends scala.reflect.Enum which does not extend js.Any. +5 | case Foo +-- Error: tests/neg-scalajs/js-enums.scala:9:5 ------------------------------------------------------------------------- +7 |@js.native +8 |@JSGlobal +9 |enum MyEnumNative extends js.Object: // error + |^ + |MyEnumNative extends scala.reflect.Enum which does not extend js.Any. +10 | case Bar +-- Error: tests/neg-scalajs/js-enums.scala:12:5 ------------------------------------------------------------------------ +12 |enum MyEnumAny extends js.Any: // error + |^ + |Non-native JS classes and objects cannot directly extend AnyRef. They must extend a JS class (native or not). +13 | case Foo +-- Error: tests/neg-scalajs/js-enums.scala:17:5 ------------------------------------------------------------------------ +15 |@js.native +16 |@JSGlobal +17 |enum MyEnumNativeAny extends js.Any: // error + |^ + |MyEnumNativeAny extends scala.reflect.Enum which does not extend js.Any. +18 | case Bar diff --git a/tests/neg-scalajs/js-enums.scala b/tests/neg-scalajs/js-enums.scala new file mode 100644 index 000000000000..cf94e690011b --- /dev/null +++ b/tests/neg-scalajs/js-enums.scala @@ -0,0 +1,18 @@ +import scala.scalajs.js +import scala.scalajs.js.annotation._ + +enum MyEnum extends js.Object: // error + case Foo + +@js.native +@JSGlobal +enum MyEnumNative extends js.Object: // error + case Bar + +enum MyEnumAny extends js.Any: // error + case Foo + +@js.native +@JSGlobal +enum MyEnumNativeAny extends js.Any: // error + case Bar diff --git a/tests/sjs-junit/test/org/scalajs/testsuite/compiler/EnumTestScala3.scala b/tests/sjs-junit/test/org/scalajs/testsuite/compiler/EnumTestScala3.scala new file mode 100644 index 000000000000..505bd2f0e28a --- /dev/null +++ b/tests/sjs-junit/test/org/scalajs/testsuite/compiler/EnumTestScala3.scala @@ -0,0 +1,123 @@ +package org.scalajs.testsuite.compiler + +import org.junit.Assert._ +import org.junit.Test + +object EnumTestScala3: + + enum Color1 derives Eql: + case Red, Green, Blue + + enum Color2 extends java.lang.Enum[Color2] derives Eql: + case Red, Green, Blue + + def color1(): Unit = + import EnumTestScala3.{Color1 => Color} + assert(Color.Red.ordinal == 0) + assert(Color.Green.ordinal == 1) + assert(Color.Blue.ordinal == 2) + assert(Color.Red.enumLabel == "Red") + assert(Color.Green.enumLabel == "Green") + assert(Color.Blue.enumLabel == "Blue") + assert(Color.valueOf("Red") == Color.Red) + assert(Color.valueOf("Green") == Color.Green) + assert(Color.valueOf("Blue") == Color.Blue) + assert(Color.valueOf("Blue") != Color.Red) + assert(Color.valueOf("Blue") != Color.Green) + assert(Color.values(0) == Color.Red) + assert(Color.values(1) == Color.Green) + assert(Color.values(2) == Color.Blue) + end color1 + + def color2(): Unit = // copied from `color1` + import EnumTestScala3.{Color2 => Color} + assert(Color.Red.ordinal == 0) + assert(Color.Green.ordinal == 1) + assert(Color.Blue.ordinal == 2) + assert(Color.Red.enumLabel == "Red") + assert(Color.Green.enumLabel == "Green") + assert(Color.Blue.enumLabel == "Blue") + assert(Color.valueOf("Red") == Color.Red) + assert(Color.valueOf("Green") == Color.Green) + assert(Color.valueOf("Blue") == Color.Blue) + assert(Color.valueOf("Blue") != Color.Red) + assert(Color.valueOf("Blue") != Color.Green) + assert(Color.values(0) == Color.Red) + assert(Color.values(1) == Color.Green) + assert(Color.values(2) == Color.Blue) + end color2 + + // test "non-simple" cases with anonymous subclasses + enum Currency1(val dollarValue: Double) derives Eql: + case Dollar extends Currency1(1.0) + case SwissFanc extends Currency1(1.09) + case Euro extends Currency1(1.18) + + enum Currency2(val dollarValue: Double) extends java.lang.Enum[Currency2] derives Eql: + case Dollar extends Currency2(1.0) + case SwissFanc extends Currency2(1.09) + case Euro extends Currency2(1.18) + + def currency1(): Unit = + import EnumTestScala3.{Currency1 => Currency} + assert(Currency.Dollar.ordinal == 0) + assert(Currency.SwissFanc.ordinal == 1) + assert(Currency.Euro.ordinal == 2) + assert(Currency.Dollar.enumLabel == "Dollar") + assert(Currency.SwissFanc.enumLabel == "SwissFanc") + assert(Currency.Euro.enumLabel == "Euro") + assert(Currency.valueOf("Dollar") == Currency.Dollar) + assert(Currency.valueOf("SwissFanc") == Currency.SwissFanc) + assert(Currency.valueOf("Euro") == Currency.Euro) + assert(Currency.valueOf("Euro") != Currency.Dollar) + assert(Currency.valueOf("Euro") != Currency.SwissFanc) + assert(Currency.values(0) == Currency.Dollar) + assert(Currency.values(1) == Currency.SwissFanc) + assert(Currency.values(2) == Currency.Euro) + assert(Currency.Dollar.dollarValue == 1.00) + assert(Currency.SwissFanc.dollarValue == 1.09) + assert(Currency.Euro.dollarValue == 1.18) + end currency1 + + def currency2(): Unit = // copied from `currency1` + import EnumTestScala3.{Currency2 => Currency} + assert(Currency.Dollar.ordinal == 0) + assert(Currency.SwissFanc.ordinal == 1) + assert(Currency.Euro.ordinal == 2) + assert(Currency.Dollar.enumLabel == "Dollar") + assert(Currency.SwissFanc.enumLabel == "SwissFanc") + assert(Currency.Euro.enumLabel == "Euro") + assert(Currency.valueOf("Dollar") == Currency.Dollar) + assert(Currency.valueOf("SwissFanc") == Currency.SwissFanc) + assert(Currency.valueOf("Euro") == Currency.Euro) + assert(Currency.valueOf("Euro") != Currency.Dollar) + assert(Currency.valueOf("Euro") != Currency.SwissFanc) + assert(Currency.values(0) == Currency.Dollar) + assert(Currency.values(1) == Currency.SwissFanc) + assert(Currency.values(2) == Currency.Euro) + assert(Currency.Dollar.dollarValue == 1.00) + assert(Currency.SwissFanc.dollarValue == 1.09) + assert(Currency.Euro.dollarValue == 1.18) + end currency2 + + enum Opt[+T]: + case Sm[+T1](value: T1) extends Opt[T1] + case Nn extends Opt[Nothing] + + def opt(): Unit = + assert(Opt.Sm(1).ordinal == 0) + assert(Opt.Nn.ordinal == 1) + assert(Opt.Sm(1).enumLabel == "Sm") + assert(Opt.Nn.enumLabel == "Nn") + assert(Opt.valueOf("Nn") == Opt.Nn) + assert(Opt.values(0) == Opt.Nn) + assert(Opt.Sm("hello").value == "hello") + end opt + +class EnumTestScala3: + import EnumTestScala3._ + @Test def testColor1(): Unit = color1() + @Test def testColor2(): Unit = color2() + @Test def testCurrency1(): Unit = currency1() + @Test def testCurrency2(): Unit = currency2() + @Test def testOpt(): Unit = opt() From 07de5229694e6fe96a94533e79cabd0e1438a769 Mon Sep 17 00:00:00 2001 From: bishabosha Date: Mon, 12 Oct 2020 13:06:21 +0200 Subject: [PATCH 4/6] add pattern match to scala.js enum test --- .../testsuite/compiler/EnumTestScala3.scala | 146 ++++++++++++------ 1 file changed, 96 insertions(+), 50 deletions(-) diff --git a/tests/sjs-junit/test/org/scalajs/testsuite/compiler/EnumTestScala3.scala b/tests/sjs-junit/test/org/scalajs/testsuite/compiler/EnumTestScala3.scala index 505bd2f0e28a..be7dae4d7af6 100644 --- a/tests/sjs-junit/test/org/scalajs/testsuite/compiler/EnumTestScala3.scala +++ b/tests/sjs-junit/test/org/scalajs/testsuite/compiler/EnumTestScala3.scala @@ -3,22 +3,23 @@ package org.scalajs.testsuite.compiler import org.junit.Assert._ import org.junit.Test -object EnumTestScala3: +class EnumTestScala3: + import EnumTestScala3._ - enum Color1 derives Eql: - case Red, Green, Blue + @Test def testColor1(): Unit = + import EnumTestScala3.{Color1 => Color} - enum Color2 extends java.lang.Enum[Color2] derives Eql: - case Red, Green, Blue + def code(c: Color): Character = c match + case Color.Red => 'R' + case Color.Green => 'G' + case Color.Blue => 'B' - def color1(): Unit = - import EnumTestScala3.{Color1 => Color} assert(Color.Red.ordinal == 0) assert(Color.Green.ordinal == 1) assert(Color.Blue.ordinal == 2) - assert(Color.Red.enumLabel == "Red") - assert(Color.Green.enumLabel == "Green") - assert(Color.Blue.enumLabel == "Blue") + assert(Color.Red.productPrefix == "Red") + assert(Color.Green.productPrefix == "Green") + assert(Color.Blue.productPrefix == "Blue") assert(Color.valueOf("Red") == Color.Red) assert(Color.valueOf("Green") == Color.Green) assert(Color.valueOf("Blue") == Color.Blue) @@ -27,16 +28,26 @@ object EnumTestScala3: assert(Color.values(0) == Color.Red) assert(Color.values(1) == Color.Green) assert(Color.values(2) == Color.Blue) - end color1 + assert(code(Color.Red) == 'R') + assert(code(Color.Green) == 'G') + assert(code(Color.Blue) == 'B') + + end testColor1 - def color2(): Unit = // copied from `color1` + @Test def testColor2(): Unit = // copied from `color1` import EnumTestScala3.{Color2 => Color} + + def code(c: Color): Character = c match + case Color.Red => 'R' + case Color.Green => 'G' + case Color.Blue => 'B' + assert(Color.Red.ordinal == 0) assert(Color.Green.ordinal == 1) assert(Color.Blue.ordinal == 2) - assert(Color.Red.enumLabel == "Red") - assert(Color.Green.enumLabel == "Green") - assert(Color.Blue.enumLabel == "Blue") + assert(Color.Red.productPrefix == "Red") + assert(Color.Green.productPrefix == "Green") + assert(Color.Blue.productPrefix == "Blue") assert(Color.valueOf("Red") == Color.Red) assert(Color.valueOf("Green") == Color.Green) assert(Color.valueOf("Blue") == Color.Blue) @@ -45,27 +56,26 @@ object EnumTestScala3: assert(Color.values(0) == Color.Red) assert(Color.values(1) == Color.Green) assert(Color.values(2) == Color.Blue) - end color2 - - // test "non-simple" cases with anonymous subclasses - enum Currency1(val dollarValue: Double) derives Eql: - case Dollar extends Currency1(1.0) - case SwissFanc extends Currency1(1.09) - case Euro extends Currency1(1.18) + assert(code(Color.Red) == 'R') + assert(code(Color.Green) == 'G') + assert(code(Color.Blue) == 'B') - enum Currency2(val dollarValue: Double) extends java.lang.Enum[Currency2] derives Eql: - case Dollar extends Currency2(1.0) - case SwissFanc extends Currency2(1.09) - case Euro extends Currency2(1.18) + end testColor2 - def currency1(): Unit = + @Test def testCurrency1(): Unit = import EnumTestScala3.{Currency1 => Currency} + + def code(c: Currency): String = c match + case Currency.Dollar => "USD" + case Currency.SwissFanc => "CHF" + case Currency.Euro => "EUR" + assert(Currency.Dollar.ordinal == 0) assert(Currency.SwissFanc.ordinal == 1) assert(Currency.Euro.ordinal == 2) - assert(Currency.Dollar.enumLabel == "Dollar") - assert(Currency.SwissFanc.enumLabel == "SwissFanc") - assert(Currency.Euro.enumLabel == "Euro") + assert(Currency.Dollar.productPrefix == "Dollar") + assert(Currency.SwissFanc.productPrefix == "SwissFanc") + assert(Currency.Euro.productPrefix == "Euro") assert(Currency.valueOf("Dollar") == Currency.Dollar) assert(Currency.valueOf("SwissFanc") == Currency.SwissFanc) assert(Currency.valueOf("Euro") == Currency.Euro) @@ -77,16 +87,27 @@ object EnumTestScala3: assert(Currency.Dollar.dollarValue == 1.00) assert(Currency.SwissFanc.dollarValue == 1.09) assert(Currency.Euro.dollarValue == 1.18) - end currency1 + assert(code(Currency.Dollar) == "USD") + assert(code(Currency.SwissFanc) == "CHF") + assert(code(Currency.Euro) == "EUR") + - def currency2(): Unit = // copied from `currency1` + end testCurrency1 + + @Test def testCurrency2(): Unit = // copied from `testCurrency1` import EnumTestScala3.{Currency2 => Currency} + + def code(c: Currency): String = c match + case Currency.Dollar => "USD" + case Currency.SwissFanc => "CHF" + case Currency.Euro => "EUR" + assert(Currency.Dollar.ordinal == 0) assert(Currency.SwissFanc.ordinal == 1) assert(Currency.Euro.ordinal == 2) - assert(Currency.Dollar.enumLabel == "Dollar") - assert(Currency.SwissFanc.enumLabel == "SwissFanc") - assert(Currency.Euro.enumLabel == "Euro") + assert(Currency.Dollar.productPrefix == "Dollar") + assert(Currency.SwissFanc.productPrefix == "SwissFanc") + assert(Currency.Euro.productPrefix == "Euro") assert(Currency.valueOf("Dollar") == Currency.Dollar) assert(Currency.valueOf("SwissFanc") == Currency.SwissFanc) assert(Currency.valueOf("Euro") == Currency.Euro) @@ -98,26 +119,51 @@ object EnumTestScala3: assert(Currency.Dollar.dollarValue == 1.00) assert(Currency.SwissFanc.dollarValue == 1.09) assert(Currency.Euro.dollarValue == 1.18) - end currency2 + assert(code(Currency.Dollar) == "USD") + assert(code(Currency.SwissFanc) == "CHF") + assert(code(Currency.Euro) == "EUR") - enum Opt[+T]: - case Sm[+T1](value: T1) extends Opt[T1] - case Nn extends Opt[Nothing] + end testCurrency2 + + @Test def testOpt(): Unit = + + def encode[T <: AnyVal](t: Opt[T]): T | Null = t match + case Opt.Sm(t) => t + case Opt.Nn => null - def opt(): Unit = assert(Opt.Sm(1).ordinal == 0) assert(Opt.Nn.ordinal == 1) - assert(Opt.Sm(1).enumLabel == "Sm") - assert(Opt.Nn.enumLabel == "Nn") + assert(Opt.Sm(1).productPrefix == "Sm") + assert(Opt.Nn.productPrefix == "Nn") assert(Opt.valueOf("Nn") == Opt.Nn) assert(Opt.values(0) == Opt.Nn) assert(Opt.Sm("hello").value == "hello") - end opt + assert(encode(Opt.Sm(23)) == 23) + assert(encode(Opt.Nn) == null) -class EnumTestScala3: - import EnumTestScala3._ - @Test def testColor1(): Unit = color1() - @Test def testColor2(): Unit = color2() - @Test def testCurrency1(): Unit = currency1() - @Test def testCurrency2(): Unit = currency2() - @Test def testOpt(): Unit = opt() + end testOpt + +object EnumTestScala3: + + enum Color1 derives Eql: + case Red, Green, Blue + + enum Color2 extends java.lang.Enum[Color2] derives Eql: + case Red, Green, Blue + + // test "non-simple" cases with anonymous subclasses + enum Currency1(val dollarValue: Double) derives Eql: + case Dollar extends Currency1(1.0) + case SwissFanc extends Currency1(1.09) + case Euro extends Currency1(1.18) + + enum Currency2(val dollarValue: Double) extends java.lang.Enum[Currency2] derives Eql: + case Dollar extends Currency2(1.0) + case SwissFanc extends Currency2(1.09) + case Euro extends Currency2(1.18) + + enum Opt[+T]: + case Sm[+T1](value: T1) extends Opt[T1] + case Nn extends Opt[Nothing] + +end EnumTestScala3 From 66eefc086d78497a38cdd5d94ee9c9284f7f26ab Mon Sep 17 00:00:00 2001 From: bishabosha Date: Mon, 12 Oct 2020 13:43:24 +0200 Subject: [PATCH 5/6] err if assign static field in other scalajs unit --- .../dotty/tools/backend/sjs/JSCodeGen.scala | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala index e32e2ddaabfc..281591b7d16a 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala @@ -802,20 +802,25 @@ class JSCodeGen()(using genCtx: Context) { if (isJSClass && f.isJSExposed) js.JSFieldDef(flags, genExpr(f.jsName)(f.sourcePos), irTpe) :: Nil else - val fieldDef = js.FieldDef(flags, encodeFieldSym(f), originalNameOfField(f), irTpe) - val rest = + val fieldIdent = encodeFieldSym(f) + val originalName = originalNameOfField(f) + val fieldDef = js.FieldDef(flags, fieldIdent, originalName, irTpe) + val optionalStaticFieldGetter = if isStaticField then + // Here we are generating a public static getter for the static field, + // this is its API for other units. This is necessary for singleton + // enum values, which are backed by static fields. val className = encodeClassName(classSym) val body = js.Block( - js.LoadModule(className), - js.SelectStatic(className, encodeFieldSym(f))(toIRType(f.info))) + js.LoadModule(className), + js.SelectStatic(className, fieldIdent)(irTpe)) js.MethodDef(js.MemberFlags.empty.withNamespace(js.MemberNamespace.PublicStatic), - encodeStaticMemberSym(f), originalNameOfField(f), Nil, toIRType(f.info), + encodeStaticMemberSym(f), originalName, Nil, irTpe, Some(body))( - OptimizerHints.empty, None) :: Nil + OptimizerHints.empty, None) :: Nil else Nil - fieldDef :: rest + fieldDef :: optionalStaticFieldGetter }).toList } @@ -1451,6 +1456,8 @@ class JSCodeGen()(using genCtx: Context) { case Assign(lhs0, rhs) => val sym = lhs0.symbol + if (sym.is(JavaStaticTerm) && sym.source != ctx.compilationUnit.source) + throw new FatalError(s"Assignment to static member ${sym.fullName} not supported") def genRhs = genExpr(rhs) val lhs = lhs0 match { case lhs: Ident => desugarIdent(lhs).getOrElse(lhs) @@ -3916,12 +3923,14 @@ class JSCodeGen()(using genCtx: Context) { (f, true) } else*/ { val f = + val className = encodeClassName(sym.owner) + val fieldIdent = encodeFieldSym(sym) + val irType = toIRType(sym.info) + if sym.is(JavaStatic) then - js.SelectStatic(encodeClassName(sym.owner), - encodeFieldSym(sym))(toIRType(sym.info)) + js.SelectStatic(className, fieldIdent)(irType) else - js.Select(qual, encodeClassName(sym.owner), - encodeFieldSym(sym))(toIRType(sym.info)) + js.Select(qual, className, fieldIdent)(irType) (f, false) } From dc31c9d49fdc05001b053fc9c2c7e487dc626d39 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Tue, 13 Oct 2020 11:37:47 +0200 Subject: [PATCH 6/6] add comment about special case --- .../tools/dotc/transform/CompleteJavaEnums.scala | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala b/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala index cf6f2788042a..9a7665d2286e 100644 --- a/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala +++ b/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala @@ -105,14 +105,18 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase => val enums = moduleCls.info.decls.filter(member => member.isAllOf(EnumValue)) for { enumValue <- enums } yield { + def forwarderSym(flags: FlagSet, info: Type): Symbol { type ThisName = TermName } = + val sym = newSymbol(clazz, enumValue.name.asTermName, flags, info) + sym.addAnnotation(Annotations.Annotation(defn.ScalaStaticAnnot)) + sym + val body = moduleRef.select(enumValue) if ctx.settings.scalajs.value then - val methodSym = newSymbol(clazz, enumValue.name.asTermName, EnumValue | Method | JavaStatic, MethodType(Nil, enumValue.info)) - methodSym.addAnnotation(Annotations.Annotation(defn.ScalaStaticAnnot)) - DefDef(methodSym, moduleRef.select(enumValue)) + // Scala.js has no support for so we must avoid assigning static fields in the enum class. + // However, since the public contract for reading static fields in the IR ABI is to call "static getters", + // we achieve the right contract with static forwarders instead. + DefDef(forwarderSym(EnumValue | Method | JavaStatic, MethodType(Nil, enumValue.info)), body) else - val fieldSym = newSymbol(clazz, enumValue.name.asTermName, EnumValue | JavaStatic, enumValue.info) - fieldSym.addAnnotation(Annotations.Annotation(defn.ScalaStaticAnnot)) - ValDef(fieldSym, moduleRef.select(enumValue)) + ValDef(forwarderSym(EnumValue | JavaStatic, enumValue.info), body) } }