@@ -45,100 +45,31 @@ object PrepJSExports {
45
45
46
46
private final case class ExportInfo (jsName : String , destination : ExportDestination )(val pos : SrcPos )
47
47
48
- /** Checks a class or module class for export .
48
+ /** Generate exports for the given Symbol .
49
49
*
50
- * Note that non-module Scala classes are never actually exported; their constructors are .
51
- * However, the checks are performed on the class when the class is annotated .
50
+ * - Registers top-level and static exports .
51
+ * - Returns (non-static) exporters for this symbol .
52
52
*/
53
- def checkClassOrModuleExports (sym : Symbol )(using Context ): Unit = {
54
- val exports = exportsOf(sym)
55
- if (exports.nonEmpty)
56
- checkClassOrModuleExports(sym, exports.head.pos)
57
- }
53
+ def genExport (sym : Symbol )(using Context ): List [Tree ] = {
54
+ // Scala classes are never exported: Their constructors are.
55
+ val isScalaClass = sym.isClass && ! sym.isOneOf(Trait | Module ) && ! isJSAny(sym)
58
56
59
- /** Generate the exporter for the given DefDef or ValDef.
60
- *
61
- * If this DefDef is a constructor, it is registered to be exported by
62
- * GenJSCode instead and no trees are returned.
63
- */
64
- def genExportMember (baseSym : Symbol )(using Context ): List [Tree ] = {
65
- val clsSym = baseSym.owner
57
+ val exports =
58
+ if (isScalaClass) Nil
59
+ else exportsOf(sym)
66
60
67
- val exports = exportsOf(baseSym)
61
+ assert(exports.isEmpty || ! sym.is(Bridge ),
62
+ s " found exports for bridge symbol $sym. exports: $exports" )
68
63
69
- // Helper function for errors
70
- def err (msg : String ): List [Tree ] = {
71
- report.error(msg, exports.head.pos)
72
- Nil
73
- }
74
-
75
- def memType = if (baseSym.isConstructor) " constructor" else " method"
76
-
77
- if (exports.isEmpty) {
78
- Nil
79
- } else if (! hasLegalExportVisibility(baseSym)) {
80
- err(s " You may only export public and protected ${memType}s " )
81
- } else if (baseSym.is(Inline )) {
82
- err(" You may not export an inline method" )
83
- } else if (isJSAny(clsSym)) {
84
- err(s " You may not export a $memType of a subclass of js.Any " )
85
- } else if (baseSym.isLocalToBlock) {
86
- err(" You may not export a local definition" )
87
- } else if (hasIllegalRepeatedParam(baseSym)) {
88
- err(s " In an exported $memType, a *-parameter must come last (through all parameter lists) " )
89
- } else if (hasIllegalDefaultParam(baseSym)) {
90
- err(s " In an exported $memType, all parameters with defaults must be at the end " )
91
- } else if (baseSym.isConstructor) {
92
- // Constructors do not need an exporter method. We only perform the checks at this phase.
93
- checkClassOrModuleExports(clsSym, exports.head.pos)
64
+ if (sym.isClass || sym.isConstructor) {
65
+ /* we can generate constructors, classes and modules entirely in the backend,
66
+ * since they do not need inheritance and such.
67
+ */
94
68
Nil
95
69
} else {
96
- assert( ! baseSym.is( Bridge ), s " genExportMember called for bridge symbol $baseSym " )
70
+ // For normal exports, generate exporter methods.
97
71
val normalExports = exports.filter(_.destination == ExportDestination .Normal )
98
- normalExports.flatMap(exp => genExportDefs(baseSym, exp.jsName, exp.pos.span))
99
- }
100
- }
101
-
102
- /** Check a class or module for export.
103
- *
104
- * There are 2 ways that this method can be reached:
105
- * - via `registerClassExports`
106
- * - via `genExportMember` (constructor of Scala class)
107
- */
108
- private def checkClassOrModuleExports (sym : Symbol , errPos : SrcPos )(using Context ): Unit = {
109
- val isMod = sym.is(ModuleClass )
110
-
111
- def err (msg : String ): Unit =
112
- report.error(msg, errPos)
113
-
114
- def hasAnyNonPrivateCtor : Boolean =
115
- sym.info.decl(nme.CONSTRUCTOR ).hasAltWith(denot => ! isPrivateMaybeWithin(denot.symbol))
116
-
117
- if (sym.is(Trait )) {
118
- err(" You may not export a trait" )
119
- } else if (sym.hasAnnotation(jsdefn.JSNativeAnnot )) {
120
- err(" You may not export a native JS " + (if (isMod) " object" else " class" ))
121
- } else if (! hasLegalExportVisibility(sym)) {
122
- err(" You may only export public and protected " + (if (isMod) " objects" else " classes" ))
123
- } else if (isJSAny(sym.owner)) {
124
- err(" You may not export a " + (if (isMod) " object" else " class" ) + " in a subclass of js.Any" )
125
- } else if (sym.isLocalToBlock) {
126
- err(" You may not export a local " + (if (isMod) " object" else " class" ))
127
- } else if (! sym.isStatic) {
128
- if (isMod)
129
- err(" You may not export a nested object" )
130
- else
131
- err(" You may not export a nested class. Create an exported factory method in the outer class to work around this limitation." )
132
- } else if (sym.is(Abstract , butNot = Trait ) && ! isJSAny(sym)) {
133
- err(" You may not export an abstract class" )
134
- } else if (! isMod && ! hasAnyNonPrivateCtor) {
135
- /* This test is only relevant for JS classes but doesn't hurt for Scala
136
- * classes as we could not reach it if there were only private
137
- * constructors.
138
- */
139
- err(" You may not export a class that has only private constructors" )
140
- } else {
141
- // OK
72
+ normalExports.flatMap(exp => genExportDefs(sym, exp.jsName, exp.pos.span))
142
73
}
143
74
}
144
75
@@ -172,8 +103,22 @@ object PrepJSExports {
172
103
Nil
173
104
}
174
105
106
+ val allAnnots = {
107
+ val allAnnots0 = directAnnots ++ unitAnnots
108
+
109
+ if (allAnnots0.nonEmpty) {
110
+ val errorPos : SrcPos =
111
+ if (allAnnots0.head.symbol == JSExportAllAnnot ) sym
112
+ else allAnnots0.head.tree
113
+ if (checkExportTarget(sym, errorPos)) allAnnots0
114
+ else Nil // prevent code generation from running to avoid crashes.
115
+ } else {
116
+ Nil
117
+ }
118
+ }
119
+
175
120
val allExportInfos = for {
176
- annot <- directAnnots ++ unitAnnots
121
+ annot <- allAnnots
177
122
} yield {
178
123
val isExportAll = annot.symbol == JSExportAllAnnot
179
124
val isTopLevelExport = annot.symbol == JSExportTopLevelAnnot
@@ -217,58 +162,29 @@ object PrepJSExports {
217
162
}
218
163
}
219
164
220
- // Enforce proper setter signature
221
- if (sym.isJSSetter)
222
- checkSetterSignature(sym, exportPos, exported = true )
223
-
224
165
// Enforce no __ in name
225
166
if (! isTopLevelExport && name.contains(" __" ))
226
167
report.error(" An exported name may not contain a double underscore (`__`)" , exportPos)
227
168
228
- /* Illegal function application exports, i.e., method named 'apply'
229
- * without an explicit export name.
230
- */
231
- if (isMember && ! hasExplicitName && sym.name == nme.apply) {
232
- destination match {
233
- case ExportDestination .Normal =>
234
- def shouldBeTolerated = {
235
- isExportAll && directAnnots.exists { annot =>
236
- annot.symbol == JSExportAnnot &&
237
- annot.arguments.nonEmpty &&
238
- annot.argumentConstantString(0 ).contains(" apply" )
239
- }
240
- }
241
-
242
- // Don't allow apply without explicit name
243
- if (! shouldBeTolerated) {
244
- report.error(
245
- " A member cannot be exported to function application. " +
246
- " Add @JSExport(\" apply\" ) to export under the name apply." ,
247
- exportPos)
248
- }
249
-
250
- case _ : ExportDestination .TopLevel =>
251
- throw new AssertionError (
252
- em " Found a top-level export without an explicit name at ${exportPos.sourcePos}" )
253
-
254
- case ExportDestination .Static =>
255
- report.error(
256
- " A member cannot be exported to function application as static. " +
257
- " Use @JSExportStatic(\" apply\" ) to export it under the name 'apply'." ,
258
- exportPos)
259
- }
260
- }
261
-
262
169
val symOwner =
263
170
if (sym.isConstructor) sym.owner.owner
264
171
else sym.owner
265
172
266
173
// Destination-specific restrictions
267
174
destination match {
268
175
case ExportDestination .Normal =>
176
+ // Disallow @JSExport at the top-level, as well as on objects and classes
177
+ if (symOwner.is(Package ) || symOwner.isPackageObject) {
178
+ report.error(" @JSExport is forbidden on top-level definitions. Use @JSExportTopLevel instead." , exportPos)
179
+ } else if (! isMember && ! sym.is(Trait )) {
180
+ report.error(
181
+ " @JSExport is forbidden on objects and classes. Use @JSExport'ed factory methods instead." ,
182
+ exportPos)
183
+ }
184
+
269
185
// Make sure we do not override the default export of toString
270
186
def isIllegalToString = {
271
- isMember && name == " toString" && sym.name != nme.toString_ &&
187
+ name == " toString" && sym.name != nme.toString_ &&
272
188
sym.info.paramInfoss.forall(_.isEmpty) && ! sym.isJSGetter
273
189
}
274
190
if (isIllegalToString) {
@@ -277,13 +193,25 @@ object PrepJSExports {
277
193
exportPos)
278
194
}
279
195
280
- // Disallow @JSExport at the top-level, as well as on objects and classes
281
- if (symOwner.is(Package ) || symOwner.isPackageObject) {
282
- report.error(" @JSExport is forbidden on top-level definitions. Use @JSExportTopLevel instead." , exportPos)
283
- } else if (! isMember && ! sym.is(Trait )) {
284
- report.error(
285
- " @JSExport is forbidden on objects and classes. Use @JSExport'ed factory methods instead." ,
286
- exportPos)
196
+ /* Illegal function application exports, i.e., method named 'apply'
197
+ * without an explicit export name.
198
+ */
199
+ if (! hasExplicitName && sym.name == nme.apply) {
200
+ def shouldBeTolerated = {
201
+ isExportAll && directAnnots.exists { annot =>
202
+ annot.symbol == JSExportAnnot &&
203
+ annot.arguments.nonEmpty &&
204
+ annot.argumentConstantString(0 ).contains(" apply" )
205
+ }
206
+ }
207
+
208
+ // Don't allow apply without explicit name
209
+ if (! shouldBeTolerated) {
210
+ report.error(
211
+ " A member cannot be exported to function application. " +
212
+ " Add @JSExport(\" apply\" ) to export under the name apply." ,
213
+ exportPos)
214
+ }
287
215
}
288
216
289
217
case _ : ExportDestination .TopLevel =>
@@ -292,10 +220,8 @@ object PrepJSExports {
292
220
else if (sym.is(Method , butNot = Accessor ) && sym.isJSProperty)
293
221
report.error(" You may not export a getter or a setter to the top level" , exportPos)
294
222
295
- /* Disallow non-static methods.
296
- * Note: Non-static classes have more specific error messages in checkClassOrModuleExports.
297
- */
298
- if (sym.isTerm && (! symOwner.isStatic || ! symOwner.is(ModuleClass )))
223
+ // Disallow non-static definitions.
224
+ if (! symOwner.isStatic || ! symOwner.is(ModuleClass ))
299
225
report.error(" Only static objects may export their members to the top level" , exportPos)
300
226
301
227
// The top-level name must be a valid JS identifier
@@ -320,11 +246,17 @@ object PrepJSExports {
320
246
if (isMember) {
321
247
if (sym.is(Lazy ))
322
248
report.error(" You may not export a lazy val as static" , exportPos)
249
+
250
+ // Illegal function application export
251
+ if (! hasExplicitName && sym.name == nme.apply) {
252
+ report.error(
253
+ " A member cannot be exported to function application as " +
254
+ " static. Use @JSExportStatic(\" apply\" ) to export it under " +
255
+ " the name 'apply'." ,
256
+ exportPos)
257
+ }
323
258
} else {
324
- if (sym.is(Trait ))
325
- report.error(" You may not export a trait as static." , exportPos)
326
- else
327
- report.error(" Implementation restriction: cannot export a class or object as static" , exportPos)
259
+ report.error(" Implementation restriction: cannot export a class or object as static" , exportPos)
328
260
}
329
261
}
330
262
@@ -342,9 +274,9 @@ object PrepJSExports {
342
274
}
343
275
.foreach(_ => report.warning(" Found duplicate @JSExport" , sym))
344
276
345
- /* Make sure that no field is exported *twice* as static, nor both as
346
- * static and as top-level (it is possible to export a field several times
347
- * as top-level, though).
277
+ /* Check that no field is exported *twice* as static, nor both as static
278
+ * and as top-level (it is possible to export a field several times as
279
+ * top-level, though).
348
280
*/
349
281
if (! sym.is(Method )) {
350
282
for (firstStatic <- allExportInfos.find(_.destination == ExportDestination .Static )) {
@@ -370,6 +302,78 @@ object PrepJSExports {
370
302
allExportInfos.distinct
371
303
}
372
304
305
+ /** Checks whether the given target is suitable for export and exporting
306
+ * should be performed.
307
+ *
308
+ * Reports any errors for unsuitable targets.
309
+ * @returns a boolean indicating whether exporting should be performed. Note:
310
+ * a result of true is not a guarantee that no error was emitted. But it is
311
+ * a guarantee that the target is not "too broken" to run the rest of
312
+ * the generation. This approximation is done to avoid having to complicate
313
+ * shared code verifying conditions.
314
+ */
315
+ private def checkExportTarget (sym : Symbol , errPos : SrcPos )(using Context ): Boolean = {
316
+ def err (msg : String ): Boolean = {
317
+ report.error(msg, errPos)
318
+ false
319
+ }
320
+
321
+ def hasLegalExportVisibility (sym : Symbol ): Boolean =
322
+ sym.isPublic || sym.is(Protected , butNot = Local )
323
+
324
+ def isMemberOfJSAny : Boolean =
325
+ isJSAny(sym.owner) || (sym.isConstructor && isJSAny(sym.owner.owner))
326
+
327
+ def hasIllegalRepeatedParam : Boolean = {
328
+ val paramInfos = sym.info.paramInfoss.flatten
329
+ paramInfos.nonEmpty && paramInfos.init.exists(_.isRepeatedParam)
330
+ }
331
+
332
+ def hasIllegalDefaultParam : Boolean = {
333
+ sym.hasDefaultParams
334
+ && sym.paramSymss.flatten.reverse.dropWhile(_.is(HasDefault )).exists(_.is(HasDefault ))
335
+ }
336
+
337
+ def hasAnyNonPrivateCtor : Boolean =
338
+ sym.info.member(nme.CONSTRUCTOR ).hasAltWith(d => ! isPrivateMaybeWithin(d.symbol))
339
+
340
+ if (sym.is(Trait )) {
341
+ err(" You may not export a trait" )
342
+ } else if (sym.hasAnnotation(jsdefn.JSNativeAnnot )) {
343
+ err(" You may not export a native JS definition" )
344
+ } else if (! hasLegalExportVisibility(sym)) {
345
+ err(" You may only export public and protected definitions" )
346
+ } else if (sym.isConstructor && ! hasLegalExportVisibility(sym.owner)) {
347
+ err(" You may only export constructors of public and protected classes" )
348
+ } else if (sym.is(Macro )) {
349
+ err(" You may not export a macro" )
350
+ } else if (isMemberOfJSAny) {
351
+ err(" You may not export a member of a subclass of js.Any" )
352
+ } else if (sym.isLocalToBlock) {
353
+ err(" You may not export a local definition" )
354
+ } else if (sym.isConstructor && sym.owner.isLocalToBlock) {
355
+ err(" You may not export constructors of local classes" )
356
+ } else if (hasIllegalRepeatedParam) {
357
+ err(" In an exported method or constructor, a *-parameter must come last " +
358
+ " (through all parameter lists)" )
359
+ } else if (hasIllegalDefaultParam) {
360
+ err(" In an exported method or constructor, all parameters with " +
361
+ " defaults must be at the end" )
362
+ } else if (sym.isConstructor && sym.owner.is(Abstract , butNot = Trait ) && ! isJSAny(sym)) {
363
+ err(" You may not export an abstract class" )
364
+ } else if (sym.isClass && ! sym.is(ModuleClass ) && isJSAny(sym) && ! hasAnyNonPrivateCtor) {
365
+ /* This test is only relevant for JS classes: We'll complain on the
366
+ * individual exported constructors in case of a Scala class.
367
+ */
368
+ err(" You may not export a class that has only private constructors" )
369
+ } else {
370
+ if (sym.isJSSetter)
371
+ checkSetterSignature(sym, errPos, exported = true )
372
+
373
+ true // ok even if a setter has the wrong signature.
374
+ }
375
+ }
376
+
373
377
/** Generates an exporter for a DefDef including default parameter methods. */
374
378
private def genExportDefs (defSym : Symbol , jsName : String , span : Span )(using Context ): List [Tree ] = {
375
379
val clsSym = defSym.owner.asClass
@@ -448,20 +452,4 @@ object PrepJSExports {
448
452
case _ =>
449
453
defn.AnyType
450
454
}
451
-
452
- /** Whether the given symbol has a visibility that allows exporting */
453
- private def hasLegalExportVisibility (sym : Symbol )(using Context ): Boolean =
454
- sym.isPublic || sym.is(Protected , butNot = Local )
455
-
456
- /** Checks whether this type has a repeated parameter elsewhere than at the end of all the params. */
457
- private def hasIllegalRepeatedParam (sym : Symbol )(using Context ): Boolean = {
458
- val paramInfos = sym.info.paramInfoss.flatten
459
- paramInfos.nonEmpty && paramInfos.init.exists(_.isRepeatedParam)
460
- }
461
-
462
- /** Checks whether there are default parameters not at the end of the flattened parameter list. */
463
- private def hasIllegalDefaultParam (sym : Symbol )(using Context ): Boolean = {
464
- sym.hasDefaultParams
465
- && sym.paramSymss.flatten.reverse.dropWhile(_.is(HasDefault )).exists(_.is(HasDefault ))
466
- }
467
455
}
0 commit comments