@@ -45,100 +45,31 @@ object PrepJSExports {
4545
4646 private final case class ExportInfo (jsName : String , destination : ExportDestination )(val pos : SrcPos )
4747
48- /** Checks a class or module class for export .
48+ /** Generate exports for the given Symbol .
4949 *
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 .
5252 */
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)
5856
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)
6660
67- val exports = exportsOf(baseSym)
61+ assert(exports.isEmpty || ! sym.is(Bridge ),
62+ s " found exports for bridge symbol $sym. exports: $exports" )
6863
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+ */
9468 Nil
9569 } else {
96- assert( ! baseSym.is( Bridge ), s " genExportMember called for bridge symbol $baseSym " )
70+ // For normal exports, generate exporter methods.
9771 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))
14273 }
14374 }
14475
@@ -172,8 +103,22 @@ object PrepJSExports {
172103 Nil
173104 }
174105
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+
175120 val allExportInfos = for {
176- annot <- directAnnots ++ unitAnnots
121+ annot <- allAnnots
177122 } yield {
178123 val isExportAll = annot.symbol == JSExportAllAnnot
179124 val isTopLevelExport = annot.symbol == JSExportTopLevelAnnot
@@ -217,58 +162,29 @@ object PrepJSExports {
217162 }
218163 }
219164
220- // Enforce proper setter signature
221- if (sym.isJSSetter)
222- checkSetterSignature(sym, exportPos, exported = true )
223-
224165 // Enforce no __ in name
225166 if (! isTopLevelExport && name.contains(" __" ))
226167 report.error(" An exported name may not contain a double underscore (`__`)" , exportPos)
227168
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-
262169 val symOwner =
263170 if (sym.isConstructor) sym.owner.owner
264171 else sym.owner
265172
266173 // Destination-specific restrictions
267174 destination match {
268175 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+
269185 // Make sure we do not override the default export of toString
270186 def isIllegalToString = {
271- isMember && name == " toString" && sym.name != nme.toString_ &&
187+ name == " toString" && sym.name != nme.toString_ &&
272188 sym.info.paramInfoss.forall(_.isEmpty) && ! sym.isJSGetter
273189 }
274190 if (isIllegalToString) {
@@ -277,13 +193,25 @@ object PrepJSExports {
277193 exportPos)
278194 }
279195
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+ }
287215 }
288216
289217 case _ : ExportDestination .TopLevel =>
@@ -292,10 +220,8 @@ object PrepJSExports {
292220 else if (sym.is(Method , butNot = Accessor ) && sym.isJSProperty)
293221 report.error(" You may not export a getter or a setter to the top level" , exportPos)
294222
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 ))
299225 report.error(" Only static objects may export their members to the top level" , exportPos)
300226
301227 // The top-level name must be a valid JS identifier
@@ -320,11 +246,17 @@ object PrepJSExports {
320246 if (isMember) {
321247 if (sym.is(Lazy ))
322248 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+ }
323258 } 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)
328260 }
329261 }
330262
@@ -342,9 +274,9 @@ object PrepJSExports {
342274 }
343275 .foreach(_ => report.warning(" Found duplicate @JSExport" , sym))
344276
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).
348280 */
349281 if (! sym.is(Method )) {
350282 for (firstStatic <- allExportInfos.find(_.destination == ExportDestination .Static )) {
@@ -370,6 +302,78 @@ object PrepJSExports {
370302 allExportInfos.distinct
371303 }
372304
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+
373377 /** Generates an exporter for a DefDef including default parameter methods. */
374378 private def genExportDefs (defSym : Symbol , jsName : String , span : Span )(using Context ): List [Tree ] = {
375379 val clsSym = defSym.owner.asClass
@@ -448,20 +452,4 @@ object PrepJSExports {
448452 case _ =>
449453 defn.AnyType
450454 }
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- }
467455}
0 commit comments