Skip to content

Commit 48fdd56

Browse files
committed
Squash: Add neg tests, and make them pass.
1 parent 4055fe3 commit 48fdd56

32 files changed

+970
-54
lines changed

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import Types._
2222
import TypeErasure.ErasedValueType
2323

2424
import dotty.tools.dotc.transform.Erasure
25-
import dotty.tools.dotc.util.SourcePosition
25+
import dotty.tools.dotc.util.{SourcePosition, SrcPos}
2626
import dotty.tools.dotc.util.Spans.Span
2727
import dotty.tools.dotc.report
2828

@@ -280,8 +280,12 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) {
280280

281281
if (conflicting.exists) {
282282
val kind = if (isProp) "property" else "method"
283-
val alts = conflicting.alternatives
284-
report.error(em"Exported $kind $jsName conflicts with ${alts.head}", alts.head.symbol.srcPos)
283+
val conflictingMember = conflicting.alternatives.head.symbol.fullName
284+
val errorPos: SrcPos = alts.map(_.symbol).filter(_.owner == classSym) match {
285+
case Nil => classSym
286+
case altsInClass => altsInClass.minBy(_.span.point)
287+
}
288+
report.error(em"Exported $kind $jsName conflicts with $conflictingMember", errorPos)
285289
}
286290

287291
genMemberExportOrDispatcher(JSName.Literal(jsName), isProp, alts.map(_.symbol), static = false)

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

Lines changed: 36 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,12 @@ object PrepJSExports {
5454

5555
private final case class ExportInfo(jsName: String, destination: ExportDestination)(val pos: SrcPos)
5656

57-
/** Checks a class or module for export.
57+
/** Checks a class or module class for export.
5858
*
59-
* Note that Scala classes are never actually exported; their constructors are.
59+
* Note that non-module Scala classes are never actually exported; their constructors are.
6060
* However, the checks are performed on the class when the class is annotated.
6161
*/
62-
def checkClassExports(sym: Symbol)(using Context): Unit = {
62+
def checkClassOrModuleExports(sym: Symbol)(using Context): Unit = {
6363
val exports = exportsOf(sym)
6464
if (exports.nonEmpty)
6565
checkClassOrModuleExports(sym, exports.head.pos)
@@ -129,6 +129,8 @@ object PrepJSExports {
129129
err("You may not export a native JS " + (if (isMod) "object" else "class"))
130130
} else if (!hasLegalExportVisibility(sym)) {
131131
err("You may only export public and protected " + (if (isMod) "objects" else "classes"))
132+
} else if (isJSAny(sym.owner)) {
133+
err("You may not export a " + (if (isMod) "object" else "class") + " in a subclass of js.Any")
132134
} else if (sym.isLocalToBlock) {
133135
err("You may not export a local " + (if (isMod) "object" else "class"))
134136
} else if (!sym.isStatic) {
@@ -187,8 +189,10 @@ object PrepJSExports {
187189
val isStaticExport = annot.symbol == JSExportStaticAnnot
188190
val hasExplicitName = annot.arguments.nonEmpty
189191

192+
val exportPos: SrcPos = if (isExportAll) sym else annot.tree
193+
190194
assert(!isTopLevelExport || hasExplicitName,
191-
em"Found a top-level export without an explicit name at ${annot.tree.sourcePos}")
195+
em"Found a top-level export without an explicit name at ${exportPos.sourcePos}")
192196

193197
val name = {
194198
if (hasExplicitName) {
@@ -224,14 +228,11 @@ object PrepJSExports {
224228

225229
// Enforce proper setter signature
226230
if (sym.isJSSetter)
227-
checkSetterSignature(sym, annot.tree, exported = true)
231+
checkSetterSignature(sym, exportPos, exported = true)
228232

229233
// Enforce no __ in name
230-
if (!isTopLevelExport && name.contains("__")) {
231-
// Get position for error message
232-
val pos: SrcPos = if (hasExplicitName) annot.arguments.head else trgSym
233-
report.error("An exported name may not contain a double underscore (`__`)", pos)
234-
}
234+
if (!isTopLevelExport && name.contains("__"))
235+
report.error("An exported name may not contain a double underscore (`__`)", exportPos)
235236

236237
/* Illegal function application exports, i.e., method named 'apply'
237238
* without an explicit export name.
@@ -249,24 +250,21 @@ object PrepJSExports {
249250

250251
// Don't allow apply without explicit name
251252
if (!shouldBeTolerated) {
252-
// Get position for error message
253-
val pos: SrcPos = if (isExportAll) trgSym else annot.tree
254-
255253
report.error(
256254
"A member cannot be exported to function application. " +
257255
"Add @JSExport(\"apply\") to export under the name apply.",
258-
pos)
256+
exportPos)
259257
}
260258

261259
case _: ExportDestination.TopLevel =>
262260
throw new AssertionError(
263-
em"Found a top-level export without an explicit name at ${annot.tree.sourcePos}")
261+
em"Found a top-level export without an explicit name at ${exportPos.sourcePos}")
264262

265263
case ExportDestination.Static =>
266264
report.error(
267265
"A member cannot be exported to function application as static. " +
268266
"Use @JSExportStatic(\"apply\") to export it under the name 'apply'.",
269-
annot.tree)
267+
exportPos)
270268
}
271269
}
272270

@@ -285,42 +283,33 @@ object PrepJSExports {
285283
if (isIllegalToString) {
286284
report.error(
287285
"You may not export a zero-argument method named other than 'toString' under the name 'toString'",
288-
annot.tree)
286+
exportPos)
289287
}
290288

291-
// Disallow @JSExport on non-members.
292-
if (!isMember && !sym.is(Trait)) {
289+
// Disallow @JSExport at the top-level, as well as on objects and classes
290+
if (symOwner.is(Package) || symOwner.isPackageObject) {
291+
report.error("@JSExport is forbidden on top-level definitions. Use @JSExportTopLevel instead.", exportPos)
292+
} else if (!isMember && !sym.is(Trait)) {
293293
report.error(
294-
"@JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead.",
295-
annot.tree)
294+
"@JSExport is forbidden on objects and classes. Use @JSExport'ed factory methods instead.",
295+
exportPos)
296296
}
297297

298298
case _: ExportDestination.TopLevel =>
299-
if (sym.is(Lazy)) {
300-
report.error(
301-
"You may not export a lazy val to the top level",
302-
annot.tree)
303-
} else if (!sym.isOneOf(Accessor | Module) && sym.isJSProperty) {
304-
report.error(
305-
"You may not export a getter or a setter to the top level",
306-
annot.tree)
307-
}
299+
if (sym.is(Lazy))
300+
report.error("You may not export a lazy val to the top level", exportPos)
301+
else if (!sym.is(Accessor) && sym.isTerm && sym.isJSProperty)
302+
report.error("You may not export a getter or a setter to the top level", exportPos)
308303

309304
/* Disallow non-static methods.
310305
* Note: Non-static classes have more specific error messages in checkClassOrModuleExports.
311306
*/
312-
if (sym.is(Method) && (!symOwner.isStatic || !symOwner.is(ModuleClass))) {
313-
report.error(
314-
"Only static objects may export their members to the top level",
315-
annot.tree)
316-
}
307+
if (sym.isTerm && (!symOwner.isStatic || !symOwner.is(ModuleClass)))
308+
report.error("Only static objects may export their members to the top level", exportPos)
317309

318310
// The top-level name must be a valid JS identifier
319-
if (!isValidTopLevelExportName(name)) {
320-
report.error(
321-
"The top-level export name must be a valid JavaScript identifier name",
322-
annot.tree)
323-
}
311+
if (!isValidTopLevelExportName(name))
312+
report.error("The top-level export name must be a valid JavaScript identifier name", exportPos)
324313

325314
case ExportDestination.Static =>
326315
def companionIsNonNativeJSClass: Boolean = {
@@ -334,21 +323,21 @@ object PrepJSExports {
334323
if (!symOwner.isStatic || !symOwner.is(ModuleClass) || !companionIsNonNativeJSClass) {
335324
report.error(
336325
"Only a static object whose companion class is a non-native JS class may export its members as static.",
337-
annot.tree)
326+
exportPos)
338327
}
339328

340329
if (isMember) {
341330
if (sym.is(Lazy))
342-
report.error("You may not export a lazy val as static", annot.tree)
331+
report.error("You may not export a lazy val as static", exportPos)
343332
} else {
344333
if (sym.is(Trait))
345-
report.error("You may not export a trait as static.", annot.tree)
334+
report.error("You may not export a trait as static.", exportPos)
346335
else
347-
report.error("Implementation restriction: cannot export a class or object as static", annot.tree)
336+
report.error("Implementation restriction: cannot export a class or object as static", exportPos)
348337
}
349338
}
350339

351-
ExportInfo(name, destination)(annot.tree)
340+
ExportInfo(name, destination)(exportPos)
352341
}
353342

354343
allExportInfos.filter(_.destination == ExportDestination.Normal)
@@ -481,9 +470,7 @@ object PrepJSExports {
481470

482471
/** Checks whether there are default parameters not at the end of the flattened parameter list. */
483472
private def hasIllegalDefaultParam(sym: Symbol)(using Context): Boolean = {
484-
// TODO
485-
//val isDefParam = (_: Symbol).hasFlag(Flags.DEFAULTPARAM)
486-
//sym.paramss.flatten.reverse.dropWhile(isDefParam).exists(isDefParam)
487-
false
473+
sym.hasDefaultParams
474+
&& sym.paramSymss.flatten.reverse.dropWhile(_.is(HasDefault)).exists(_.is(HasDefault))
488475
}
489476
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,7 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP
145145

146146
tree match {
147147
case tree: TypeDef if tree.isClassDef =>
148-
if (!sym.is(ModuleClass))
149-
checkClassExports(sym)
148+
checkClassOrModuleExports(sym)
150149

151150
if (isJSAny(sym))
152151
transformJSClassDef(tree)
@@ -228,6 +227,8 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP
228227
exporters.get(clsSym).fold {
229228
transformedTree
230229
} { exports =>
230+
checkNoDoubleDeclaration(clsSym)
231+
231232
cpy.Template(transformedTree)(
232233
transformedTree.constr,
233234
transformedTree.parents,
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import scala.scalajs.js
2+
import scala.scalajs.js.annotation._
3+
4+
class A {
5+
@JSExport("toString") // error
6+
def a(): Int = 5
7+
}
8+
9+
class B {
10+
@JSExport("toString") // ok
11+
def a(x: Int): Int = x + 1
12+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import scala.scalajs.js
2+
import scala.scalajs.js.annotation._
3+
4+
class A1 {
5+
@JSExport("a") // error
6+
def bar(): Int = 2
7+
8+
@JSExport("a") // error
9+
val foo = 1
10+
}
11+
12+
class B1 {
13+
@JSExport("a")
14+
def bar(): Int = 2
15+
}
16+
17+
class B2 extends B1 {
18+
@JSExport("a") // error
19+
def foo_=(x: Int): Unit = ()
20+
21+
@JSExport("a")
22+
val foo = 1
23+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import scala.scalajs.js
2+
import scala.scalajs.js.annotation._
3+
4+
class A {
5+
@JSExport
6+
def rtType(x: js.Any): js.Any = x
7+
8+
@JSExport // error
9+
def rtType(x: js.Dynamic): js.Dynamic = x
10+
}
11+
12+
class B {
13+
@JSExport
14+
def foo(x: Int)(ys: Int*): Int = x
15+
16+
@JSExport // error
17+
def foo(x: Int*): Seq[Int] = x
18+
}
19+
20+
class C {
21+
@JSExport
22+
def foo(x: Int = 1): Int = x
23+
@JSExport // error
24+
def foo(x: String*): Seq[String] = x
25+
}
26+
27+
class D {
28+
@JSExport
29+
def foo(x: Double, y: String)(z: Int = 1): Double = x
30+
@JSExport // error
31+
def foo(x: Double, y: String)(z: String*): Double = x
32+
}
33+
34+
class E {
35+
@JSExport
36+
def a(x: scala.scalajs.js.Any): Int = 1
37+
38+
@JSExport // error
39+
def a(x: Any): Int = 2
40+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import scala.scalajs.js
2+
import scala.scalajs.js.annotation._
3+
4+
class A {
5+
@JSExport("value") // error
6+
def hello: String = "foo"
7+
8+
@JSExport("value")
9+
def world: String = "bar"
10+
}
11+
12+
class B {
13+
class Box[T](val x: T)
14+
15+
@JSExport // error
16+
def ub(x: Box[String]): String = x.x
17+
@JSExport
18+
def ub(x: Box[Int]): Int = x.x
19+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import scala.scalajs.js
2+
import scala.scalajs.js.annotation._
3+
4+
class A {
5+
@JSExport(name = "__") // error
6+
def foo: Int = 1
7+
8+
@JSExport // error
9+
def bar__(x: Int): Int = x
10+
}
11+
12+
object B {
13+
@JSExportTopLevel(name = "__") // ok
14+
val foo: Int = 1
15+
16+
@JSExportTopLevel("bar__") // ok
17+
def bar(x: Int): Int = x
18+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import scala.scalajs.js
2+
import scala.scalajs.js.annotation._
3+
4+
class A {
5+
@JSExport // error
6+
def apply(): Int = 1
7+
}
8+
9+
@JSExportAll
10+
class B {
11+
def apply(): Int = 1 // error
12+
}
13+
14+
@JSExportAll
15+
class C {
16+
@JSExport("foo")
17+
def apply(): Int = 1 // error
18+
}
19+
20+
@JSExportAll
21+
class D {
22+
@JSExport("apply")
23+
def apply(): Int = 1 // ok
24+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import scala.scalajs.js
2+
import scala.scalajs.js.annotation._
3+
4+
@js.native
5+
@JSGlobal
6+
class A extends js.Object {
7+
@JSExport // error
8+
def foo: Int = js.native
9+
}
10+
11+
class B extends js.Object {
12+
@JSExport // error
13+
def foo: Int = js.native
14+
}

0 commit comments

Comments
 (0)