Skip to content

Commit 18ebc56

Browse files
committed
prevent exports in JS non-native traits
1 parent ce351db commit 18ebc56

File tree

5 files changed

+97
-34
lines changed

5 files changed

+97
-34
lines changed

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,6 @@ final class JSDefinitions()(using Context) {
105105
@threadUnsafe lazy val ExposedJSMemberAnnotType: TypeRef = requiredClassRef("scala.scalajs.js.annotation.internal.ExposedJSMember")
106106
def ExposedJSMemberAnnot(using Context) = ExposedJSMemberAnnotType.symbol.asClass
107107

108-
def JSAnnotInternalPackage(using Context) = JSTypeAnnot.owner.asClass
109-
110108
@threadUnsafe lazy val JSImportNamespaceModuleRef = requiredModuleRef("scala.scalajs.js.annotation.JSImport.Namespace")
111109
def JSImportNamespaceModule(using Context) = JSImportNamespaceModuleRef.symbol
112110

compiler/src/dotty/tools/dotc/transform/sjs/PrepJSInterop.scala

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP
8585
private def anyEnclosingOwner: OwnerKind = allEnclosingOwners
8686

8787
private def enterOwner[A](kind: OwnerKind)(body: => A): A = {
88-
require(kind.isBaseKind, kind)
88+
require(kind.withoutMods.isBaseKind, kind.withoutMods)
8989
val oldEnclosingOwner = enclosingOwner
9090
val oldAllEnclosingOwners = allEnclosingOwners
9191
enclosingOwner = kind
@@ -302,8 +302,10 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP
302302
super.transform(tree)
303303

304304
case _: Export =>
305-
if anyEnclosingOwner is OwnerKind.JSNative then
305+
if enclosingOwner is OwnerKind.JSNative then
306306
report.error("Native JS traits, classes and objects cannot contain exported definitions.", tree)
307+
else if enclosingOwner is OwnerKind.WithJSTrait then
308+
report.error("Non-native JS traits cannot contain exported definitions.", tree)
307309

308310
super.transform(tree)
309311

@@ -465,6 +467,7 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP
465467
val kind = {
466468
if (!isJSNative) {
467469
if (sym.is(ModuleClass)) OwnerKind.JSMod
470+
else if (sym.is(Trait)) OwnerKind.JSClass | OwnerKind.WithJSTrait
468471
else OwnerKind.JSClass
469472
} else {
470473
if (sym.is(ModuleClass)) OwnerKind.JSNativeMod
@@ -825,38 +828,38 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP
825828
/** Removes annotations from exported definitions (e.g. `export foo.bar`):
826829
* - `js.native`
827830
* - `js.annotation.*`
828-
* - `js.annotation.internal.*`
829831
*/
830832
private def stripJSAnnotsOnExported(sym: Symbol)(using Context): Unit =
831833
if !sym.is(Exported) then return
832834

833835
val JSNativeAnnot = jsdefn.JSNativeAnnot
834836
val JSAnnotPackage = jsdefn.JSAnnotPackage
835-
val JSAnnotInternalPackage = jsdefn.JSAnnotInternalPackage
836837

837838
extension (sym: Symbol) def isJSAnnot =
838-
(sym eq JSNativeAnnot) || (sym.owner eq JSAnnotPackage) || (sym.owner eq JSAnnotInternalPackage)
839+
(sym eq JSNativeAnnot) || (sym.owner eq JSAnnotPackage)
839840

840841
val newAnnots = sym.annotations.filterConserve(!_.symbol.isJSAnnot)
841842
if newAnnots ne sym.annotations then
842843
sym.annotations = newAnnots
843844
end stripJSAnnotsOnExported
844845

845846
private def checkRHSCallsJSNative(tree: ValOrDefDef, longKindStr: String)(using Context): Unit = {
846-
if !tree.symbol.is(Exported) then
847-
// Check that the rhs is exactly `= js.native`
848-
tree.rhs match {
849-
case sel: Select if sel.symbol == jsdefn.JSPackage_native =>
850-
// ok
851-
case _ =>
852-
val pos = if (tree.rhs != EmptyTree) tree.rhs.srcPos else tree.srcPos
853-
report.error(s"$longKindStr may only call js.native.", pos)
854-
}
847+
if tree.symbol.is(Exported) then
848+
return // we already report an error that exports are not allowed here, this prevents extra errors.
849+
850+
// Check that the rhs is exactly `= js.native`
851+
tree.rhs match {
852+
case sel: Select if sel.symbol == jsdefn.JSPackage_native =>
853+
// ok
854+
case _ =>
855+
val pos = if (tree.rhs != EmptyTree) tree.rhs.srcPos else tree.srcPos
856+
report.error(s"$longKindStr may only call js.native.", pos)
857+
}
855858

856-
// Check that the resul type was explicitly specified
857-
// (This is stronger than Scala 2, which only warns, and only if it was inferred as Nothing.)
858-
if (tree.tpt.span.isSynthetic)
859-
report.error(i"The type of ${tree.name} must be explicitly specified because it is JS native.", tree)
859+
// Check that the resul type was explicitly specified
860+
// (This is stronger than Scala 2, which only warns, and only if it was inferred as Nothing.)
861+
if (tree.tpt.span.isSynthetic)
862+
report.error(i"The type of ${tree.name} must be explicitly specified because it is JS native.", tree)
860863
}
861864

862865
private def checkJSNativeSpecificAnnotsOnNonJSNative(memberDef: MemberDef)(using Context): Unit = {
@@ -996,6 +999,9 @@ object PrepJSInterop {
996999
inline def isBaseKind: Boolean =
9971000
Integer.lowestOneBit(baseKinds) == baseKinds && baseKinds != 0 // exactly 1 bit on
9981001

1002+
@inline def withoutMods: OwnerKind =
1003+
new OwnerKind(this.baseKinds & ~ModifierMask.baseKinds)
1004+
9991005
// cannot be `inline` because it accesses the private constructor
10001006
@inline def |(that: OwnerKind): OwnerKind =
10011007
new OwnerKind(this.baseKinds | that.baseKinds)
@@ -1026,6 +1032,11 @@ object PrepJSInterop {
10261032
/** A non-native JS object. */
10271033
val JSMod = new OwnerKind(0x20)
10281034

1035+
// Modifiers - these add information to a base kind
1036+
1037+
/** A modifer for `JSClass` to signal a non-native JS trait */
1038+
val WithJSTrait = new OwnerKind(0x1000)
1039+
10291040
// Compound kinds
10301041

10311042
/** A Scala class, trait or object, i.e., anything not extending js.Any. */
@@ -1038,6 +1049,9 @@ object PrepJSInterop {
10381049
/** A JS type, i.e., something extending js.Any. */
10391050
val JSType = JSNative | JSNonNative
10401051

1052+
/** A mask of all OwnerKind modifiers */
1053+
private val ModifierMask = WithJSTrait
1054+
10411055
/** Any kind of class/trait, i.e., a Scala or JS class/trait. */
10421056
val AnyClass = ScalaClass | JSNativeClass | JSClass
10431057
}
Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
-- Error: tests/neg-scalajs/js-native-exports.scala:15:11 --------------------------------------------------------------
2-
15 | export bag.{str, int} // error
3-
| ^^^^^^^^^^^^^^^^^^^^^
1+
-- Error: tests/neg-scalajs/js-native-exports.scala:17:11 --------------------------------------------------------------
2+
17 | export bag.{str, int, bool, dbl} // error
3+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
44
| Native JS traits, classes and objects cannot contain exported definitions.
5-
-- Error: tests/neg-scalajs/js-native-exports.scala:21:11 --------------------------------------------------------------
6-
21 | export bag.{str, int} // error
7-
| ^^^^^^^^^^^^^^^^^^^^^
5+
-- Error: tests/neg-scalajs/js-native-exports.scala:23:11 --------------------------------------------------------------
6+
23 | export bag.{str, int, bool, dbl} // error
7+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
88
| Native JS traits, classes and objects cannot contain exported definitions.
9-
-- Error: tests/neg-scalajs/js-native-exports.scala:28:11 --------------------------------------------------------------
10-
28 | export bag.{str, int} // error
11-
| ^^^^^^^^^^^^^^^^^^^^^
9+
-- Error: tests/neg-scalajs/js-native-exports.scala:30:11 --------------------------------------------------------------
10+
30 | export bag.{str, int, bool, dbl} // error
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1212
| Native JS traits, classes and objects cannot contain exported definitions.
13+
-- Error: tests/neg-scalajs/js-native-exports.scala:35:11 --------------------------------------------------------------
14+
35 | export bag.{str, int, bool, dbl} // error
15+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
16+
| Non-native JS traits cannot contain exported definitions.

tests/neg-scalajs/js-native-exports.scala

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,33 @@ object A {
66
@js.native
77
trait Bag extends js.Any {
88
val str: String
9-
val int: Int
9+
def int: Int
10+
def bool(): Boolean
11+
def dbl(dbl: Double): Double
1012
}
1113

1214
@js.native
1315
@JSGlobal("BagHolder_GlobalClass")
1416
final class BagHolder(val bag: Bag) extends js.Object {
15-
export bag.{str, int} // error
17+
export bag.{str, int, bool, dbl} // error
1618
}
1719

1820
@js.native
1921
trait BagHolderTrait extends js.Any {
2022
val bag: Bag
21-
export bag.{str, int} // error
23+
export bag.{str, int, bool, dbl} // error
2224
}
2325

2426
@js.native
2527
@JSGlobal("BagHolderModule_GlobalVar")
2628
object BagHolderModule extends js.Object {
2729
val bag: Bag = js.native
28-
export bag.{str, int} // error
30+
export bag.{str, int, bool, dbl} // error
31+
}
32+
33+
trait NonNativeBagHolderTrait extends js.Any {
34+
val bag: Bag
35+
export bag.{str, int, bool, dbl} // error
2936
}
3037

3138
}

tests/sjs-junit/test/org/scalajs/testsuite/jsinterop/ExportedJSNativeMembersScala3.scala

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,25 @@ object ExportedJSNativeMembersScala3:
4848
@JSGlobal("quxxInstance_GlobalThatWillBeExported")
4949
val quxxInstance: QuxHolderHolderTrait = js.native
5050

51+
@js.native
52+
@JSGlobal("addOne_GlobalThatWillBeExported")
53+
def addOne(i: Int): Int = js.native
54+
55+
}
56+
57+
object B extends js.Object {
58+
export A.FooModule // trait (native)
59+
export A.Foo // val (native)
60+
export A.Bar // object (native)
61+
export A.Baz // class (native)
62+
export A.QuxHolder // class (native)
63+
export A.QuxHolderHolder // class (native)
64+
export A.QuxHolderHolderTrait // trait (native)
65+
export A.quxxInstance // val (native)
66+
export A.addOne // def (native)
5167
}
5268

53-
object B {
69+
final class C extends js.Object {
5470
export A.FooModule // trait (native)
5571
export A.Foo // val (native)
5672
export A.Bar // object (native)
@@ -59,6 +75,7 @@ object ExportedJSNativeMembersScala3:
5975
export A.QuxHolderHolder // class (native)
6076
export A.QuxHolderHolderTrait // trait (native)
6177
export A.quxxInstance // val (native)
78+
export A.addOne // def (native)
6279
}
6380

6481
class ExportedJSNativeMembersScala3:
@@ -87,12 +104,20 @@ class ExportedJSNativeMembersScala3:
87104
new QuxHolder_GlobalThatWillBeExported("quxxInstance")
88105
)
89106
)
107+
function addOne_GlobalThatWillBeExported(i) {
108+
return i + 1;
109+
}
90110
""")
111+
112+
val C = ExportedJSNativeMembersScala3.C()
113+
91114
assertEquals("foo", A.Foo.foo)
92115
assertEquals("foo", B.Foo.foo)
116+
assertEquals("foo", C.Foo.foo)
93117

94118
assertEquals(23, A.Bar.bar)
95119
assertEquals(23, B.Bar.bar)
120+
assertEquals(23, C.Bar.bar)
96121

97122
val abaz = A.Baz("abaz1")
98123
assertEquals("abaz1", abaz.baz)
@@ -104,6 +129,11 @@ class ExportedJSNativeMembersScala3:
104129
bbaz.baz = "bbaz2"
105130
assertEquals("bbaz2", bbaz.baz)
106131

132+
val cbaz = C.Baz("cbaz1")
133+
assertEquals("cbaz1", cbaz.baz)
134+
cbaz.baz = "cbaz2"
135+
assertEquals("cbaz2", cbaz.baz)
136+
107137
val quxHolderHolderA = A.QuxHolderHolder(A.QuxHolder("quxHolderHolderA"))
108138
assertEquals("quxHolderHolderA", quxHolderHolderA.qux)
109139
assertEquals("quxHolderHolderA", quxHolderHolderA.quxHolder.qux)
@@ -112,10 +142,20 @@ class ExportedJSNativeMembersScala3:
112142
assertEquals("quxHolderHolderB", quxHolderHolderB.qux)
113143
assertEquals("quxHolderHolderB", quxHolderHolderB.quxHolder.qux)
114144

145+
val quxHolderHolderC = C.QuxHolderHolder(C.QuxHolder("quxHolderHolderC"))
146+
assertEquals("quxHolderHolderC", quxHolderHolderC.qux)
147+
assertEquals("quxHolderHolderC", quxHolderHolderC.quxHolder.qux)
148+
115149
assertEquals("quxxInstance", A.quxxInstance.qux)
116150
assertEquals("quxxInstance", A.quxxInstance.quxHolder.qux)
117151
assertEquals("quxxInstance", B.quxxInstance.qux)
118152
assertEquals("quxxInstance", B.quxxInstance.quxHolder.qux)
153+
assertEquals("quxxInstance", C.quxxInstance.qux)
154+
assertEquals("quxxInstance", C.quxxInstance.quxHolder.qux)
155+
156+
assertEquals(2, A.addOne(1))
157+
assertEquals(3, B.addOne(2))
158+
assertEquals(4, C.addOne(3))
119159
}
120160

121161
end ExportedJSNativeMembersScala3

0 commit comments

Comments
 (0)