@@ -54,23 +54,19 @@ object PrepJSExports {
5454 // Scala classes are never exported: Their constructors are.
5555 val isScalaClass = sym.isClass && !sym.isOneOf(Trait | Module) && !isJSAny(sym)
5656
57+ // Filter constructors of module classes: The module classes themselves will be exported.
58+ val isModuleClassCtor = sym.isConstructor && sym.owner.is(ModuleClass)
59+
5760 val exports =
58- if (isScalaClass) Nil
61+ if (isScalaClass || isModuleClassCtor ) Nil
5962 else exportsOf(sym)
6063
6164 assert(exports.isEmpty || !sym.is(Bridge),
6265 s"found exports for bridge symbol $sym. exports: $exports")
6366
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- */
68- Nil
69- } else {
70- // For normal exports, generate exporter methods.
71- val normalExports = exports.filter(_.destination == ExportDestination.Normal)
72- normalExports.flatMap(exp => genExportDefs(sym, exp.jsName, exp.pos.span))
73- }
67+ // For normal exports, generate exporter methods.
68+ val normalExports = exports.filter(_.destination == ExportDestination.Normal)
69+ normalExports.flatMap(exp => genExportDefs(sym, exp.jsName, exp.pos.span))
7470 }
7571
7672 /** Computes the ExportInfos for sym from its annotations. */
@@ -83,6 +79,10 @@ object PrepJSExports {
8379 else sym
8480 }
8581
82+ val symOwner =
83+ if (sym.isConstructor) sym.owner.owner
84+ else sym.owner
85+
8686 val JSExportAnnot = jsdefn.JSExportAnnot
8787 val JSExportTopLevelAnnot = jsdefn.JSExportTopLevelAnnot
8888 val JSExportStaticAnnot = jsdefn.JSExportStaticAnnot
@@ -92,13 +92,22 @@ object PrepJSExports {
9292 val directMemberAnnots = Set[Symbol](JSExportAnnot, JSExportTopLevelAnnot, JSExportStaticAnnot)
9393 val directAnnots = trgSym.annotations.filter(annot => directMemberAnnots.contains(annot.symbol))
9494
95- // Is this a member export (i.e. not a class or module export)?
96- val isMember = !sym.isClass && !sym.isConstructor
97-
98- // Annotations for this member on the whole unit
95+ /* Annotations for this member on the whole unit
96+ *
97+ * Note that for top-level classes / modules this is always empty, because
98+ * packages cannot have annotations.
99+ */
99100 val unitAnnots = {
100- if (isMember && sym.isPublic && !sym.is(Synthetic))
101- sym.owner.annotations.filter(_.symbol == JSExportAllAnnot)
101+ val useExportAll = {
102+ sym.isPublic &&
103+ !sym.is(Synthetic) &&
104+ !sym.isConstructor &&
105+ !sym.is(Trait) &&
106+ (!sym.isClass || sym.is(ModuleClass))
107+ }
108+
109+ if (useExportAll)
110+ symOwner.annotations.filter(_.symbol == JSExportAllAnnot)
102111 else
103112 Nil
104113 }
@@ -139,7 +148,13 @@ object PrepJSExports {
139148 "dummy"
140149 }
141150 } else {
142- sym.defaultJSName
151+ val name = (if (sym.isConstructor) sym.owner else sym).defaultJSName
152+ if (name.endsWith(str.SETTER_SUFFIX) && !sym.isJSSetter) {
153+ report.error(
154+ "You must set an explicit name when exporting a non-setter with a name ending in _=",
155+ exportPos)
156+ }
157+ name
143158 }
144159 }
145160
@@ -166,20 +181,12 @@ object PrepJSExports {
166181 if (!isTopLevelExport && name.contains("__"))
167182 report.error("An exported name may not contain a double underscore (`__`)", exportPos)
168183
169- val symOwner =
170- if (sym.isConstructor) sym.owner.owner
171- else sym.owner
172-
173184 // Destination-specific restrictions
174185 destination match {
175186 case ExportDestination.Normal =>
176- // Disallow @JSExport at the top-level, as well as on objects and classes
187+ // Disallow @JSExport on top-level definitions.
177188 if (symOwner.is(Package) || symOwner.isPackageObject) {
178189 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)
183190 }
184191
185192 // Make sure we do not override the default export of toString
@@ -243,19 +250,19 @@ object PrepJSExports {
243250 exportPos)
244251 }
245252
246- if (isMember) {
247- if (sym.is(Lazy))
248- report.error("You may not export a lazy val as static", exportPos)
253+ if (sym.is(Lazy))
254+ report.error("You may not export a lazy val as static", exportPos)
249255
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- }
258- } else {
256+ // Illegal function application export
257+ if (!hasExplicitName && sym.name == nme.apply) {
258+ report.error(
259+ "A member cannot be exported to function application as " +
260+ "static. Use @JSExportStatic(\"apply\") to export it under " +
261+ "the name 'apply'.",
262+ exportPos)
263+ }
264+
265+ if (sym.isClass || sym.isConstructor) {
259266 report.error("Implementation restriction: cannot export a class or object as static", exportPos)
260267 }
261268 }
@@ -375,31 +382,41 @@ object PrepJSExports {
375382 }
376383
377384 /** Generates an exporter for a DefDef including default parameter methods. */
378- private def genExportDefs(defSym: Symbol, jsName: String, span: Span)(using Context): List[Tree] = {
379- val clsSym = defSym.owner.asClass
385+ private def genExportDefs(sym: Symbol, jsName: String, span: Span)(using Context): List[Tree] = {
386+ val siblingSym =
387+ if (sym.isConstructor) sym.owner
388+ else sym
389+
390+ val clsSym = siblingSym.owner.asClass
391+
392+ val isProperty = sym.is(ModuleClass) || isJSAny(sym) || sym.isJSProperty
393+
394+ val copiedFlags0 = (siblingSym.flags & (Protected | Final)).toTermFlags
395+ val copiedFlags =
396+ if (siblingSym.is(HasDefaultParams)) copiedFlags0 | HasDefaultParams // term flag only
397+ else copiedFlags0
380398
381399 // Create symbol for new method
382- val name = makeExportName(jsName, !defSym.is(Method) || defSym.isJSProperty)
383- val flags = (defSym.flags | Method | Synthetic)
384- &~ (Deferred | Accessor | ParamAccessor | CaseAccessor | Mutable | Lazy | Override)
400+ val scalaName = makeExportName(jsName, !sym.is(Method) || sym.isJSProperty)
401+ val flags = Method | Synthetic | copiedFlags
385402 val info =
386- if (defSym .isConstructor) defSym .info
387- else if (defSym .is(Method)) finalResultTypeToAny(defSym .info)
403+ if (sym .isConstructor) sym .info
404+ else if (sym .is(Method)) finalResultTypeToAny(sym .info)
388405 else ExprType(defn.AnyType)
389- val expSym = newSymbol(clsSym, name , flags, info, defSym .privateWithin, span).entered
406+ val expSym = newSymbol(clsSym, scalaName , flags, info, sym .privateWithin, span).entered
390407
391408 // Construct exporter DefDef tree
392- val exporter = genProxyDefDef(clsSym, defSym , expSym, span)
409+ val exporter = genProxyDefDef(clsSym, sym , expSym, span)
393410
394411 // Construct exporters for default getters
395- val defaultGetters = if (!defSym .hasDefaultParams) {
412+ val defaultGetters = if (!sym .hasDefaultParams) {
396413 Nil
397414 } else {
398415 for {
399- (param, i) <- defSym .paramSymss.flatten.zipWithIndex
416+ (param, i) <- sym .paramSymss.flatten.zipWithIndex
400417 if param.is(HasDefault)
401418 } yield {
402- genExportDefaultGetter(clsSym, defSym , expSym, i, span)
419+ genExportDefaultGetter(clsSym, sym , expSym, i, span)
403420 }
404421 }
405422
@@ -435,7 +452,27 @@ object PrepJSExports {
435452 proxySym: TermSymbol, span: Span)(using Context): Tree = {
436453
437454 DefDef(proxySym, { argss =>
438- This(clsSym).select(trgSym).appliedToArgss(argss)
455+ if (trgSym.isConstructor) {
456+ val tycon = trgSym.owner.typeRef
457+ New(tycon).select(TermRef(tycon, trgSym)).appliedToArgss(argss)
458+ } else if (trgSym.is(ModuleClass)) {
459+ assert(argss.isEmpty,
460+ s"got a module export with non-empty paramss. target: $trgSym, proxy: $proxySym at $span")
461+ ref(trgSym.sourceModule)
462+ } else if (trgSym.isClass) {
463+ assert(isJSAny(trgSym), s"got a class export for a non-JS class ($trgSym) at $span")
464+ val tpe = argss match {
465+ case Nil =>
466+ trgSym.typeRef
467+ case (targs @ (first :: _)) :: Nil if first.isType =>
468+ trgSym.typeRef.appliedTo(targs.map(_.tpe))
469+ case _ =>
470+ throw AssertionError(s"got a class export with unexpected paramss. target: $trgSym, proxy: $proxySym at $span")
471+ }
472+ ref(jsdefn.JSPackage_constructorOf).appliedToType(tpe)
473+ } else {
474+ This(clsSym).select(trgSym).appliedToArgss(argss)
475+ }
439476 }).withSpan(span)
440477 }
441478
0 commit comments