Skip to content

Commit 49e853c

Browse files
authored
Merge pull request #10164 from dotty-staging/sjs-js-exports
Scala.js: Implement JS exports.
2 parents 66eee84 + 69afa9c commit 49e853c

40 files changed

+1856
-94
lines changed

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

Lines changed: 62 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,6 @@ class JSCodeGen()(using genCtx: Context) {
323323
// Generate members (constructor + methods)
324324

325325
val generatedNonFieldMembers = new mutable.ListBuffer[js.MemberDef]
326-
val exportedSymbols = new mutable.ListBuffer[Symbol]
327326

328327
val tpl = td.rhs.asInstanceOf[Template]
329328
for (tree <- tpl.constr :: tpl.body) {
@@ -336,19 +335,11 @@ class JSCodeGen()(using genCtx: Context) {
336335
case dd: DefDef =>
337336
val sym = dd.symbol
338337

339-
val isExport = false //jsInterop.isExport(sym)
340-
341338
if (sym.hasAnnotation(jsdefn.JSNativeAnnot))
342339
generatedNonFieldMembers += genJSNativeMemberDef(dd)
343340
else
344341
generatedNonFieldMembers ++= genMethod(dd)
345342

346-
if (isExport) {
347-
// We add symbols that we have to export here. This way we also
348-
// get inherited stuff that is implemented in this class.
349-
exportedSymbols += sym
350-
}
351-
352343
case _ =>
353344
throw new FatalError("Illegal tree in body of genScalaClass(): " + tree)
354345
}
@@ -357,21 +348,11 @@ class JSCodeGen()(using genCtx: Context) {
357348
// Generate fields and add to methods + ctors
358349
val generatedMembers = genClassFields(td) ++ generatedNonFieldMembers.toList
359350

360-
// Generate the exported members, constructors and accessors
361-
val exports = {
362-
/*
363-
// Generate the exported members
364-
val memberExports = genMemberExports(sym, exportedSymbols.toList)
351+
// Generate member exports
352+
val memberExports = jsExportsGen.genMemberExports(sym)
365353

366-
// Generate exported constructors or accessors
367-
val exportedConstructorsOrAccessors =
368-
if (isStaticModule(sym)) genModuleAccessorExports(sym)
369-
else genConstructorExports(sym)
370-
371-
memberExports ++ exportedConstructorsOrAccessors
372-
*/
373-
Nil
374-
}
354+
// Generate top-level export definitions
355+
val topLevelExportDefs = jsExportsGen.genTopLevelExports(sym)
375356

376357
// Static initializer
377358
val optStaticInitializer = {
@@ -383,20 +364,27 @@ class JSCodeGen()(using genCtx: Context) {
383364
}
384365
}
385366
if (enableReflectiveInstantiation)
386-
genRegisterReflectiveInstantiation(sym)
367+
genRegisterReflectiveInstantiation(sym).toList
387368
else
388-
None
369+
Nil
389370
}
390371

391-
val staticInitializerStats = reflectInit.toList
372+
// Initialization of the module because of field exports
373+
val needsStaticModuleInit =
374+
topLevelExportDefs.exists(_.isInstanceOf[js.TopLevelFieldExportDef])
375+
val staticModuleInit =
376+
if (!needsStaticModuleInit) Nil
377+
else List(genLoadModule(sym))
378+
379+
val staticInitializerStats = reflectInit ::: staticModuleInit
392380
if (staticInitializerStats.nonEmpty)
393-
List(genStaticInitializerWithStats(js.Block(staticInitializerStats)))
381+
List(genStaticConstructorWithStats(ir.Names.StaticInitializerName, js.Block(staticInitializerStats)))
394382
else
395383
Nil
396384
}
397385

398386
val allMemberDefsExceptStaticForwarders =
399-
generatedMembers ::: exports ::: optStaticInitializer
387+
generatedMembers ::: memberExports ::: optStaticInitializer
400388

401389
// Add static forwarders
402390
val allMemberDefs = if (!isCandidateForForwarders(sym)) {
@@ -452,7 +440,7 @@ class JSCodeGen()(using genCtx: Context) {
452440
None,
453441
None,
454442
hashedDefs,
455-
Nil)(
443+
topLevelExportDefs)(
456444
optimizerHints)
457445

458446
classDefinition
@@ -511,6 +499,28 @@ class JSCodeGen()(using genCtx: Context) {
511499
}
512500
}
513501

502+
// Static members (exported from the companion object)
503+
val staticMembers = {
504+
val module = sym.companionModule
505+
if (!module.exists) {
506+
Nil
507+
} else {
508+
val companionModuleClass = module.moduleClass
509+
val exports = withScopedVars(currentClassSym := companionModuleClass) {
510+
jsExportsGen.genStaticExports(companionModuleClass)
511+
}
512+
if (exports.exists(_.isInstanceOf[js.JSFieldDef])) {
513+
val classInitializer =
514+
genStaticConstructorWithStats(ir.Names.ClassInitializerName, genLoadModule(companionModuleClass))
515+
exports :+ classInitializer
516+
} else {
517+
exports
518+
}
519+
}
520+
}
521+
522+
val topLevelExports = jsExportsGen.genTopLevelExports(sym)
523+
514524
val (jsClassCaptures, generatedConstructor) =
515525
genJSClassCapturesAndConstructor(sym, constructorTrees.toList)
516526

@@ -525,7 +535,8 @@ class JSCodeGen()(using genCtx: Context) {
525535
genClassFields(td) :::
526536
generatedConstructor ::
527537
jsExportsGen.genJSClassDispatchers(sym, dispatchMethodNames.result().distinct) :::
528-
generatedMethods.toList
538+
generatedMethods.toList :::
539+
staticMembers
529540
}
530541

531542
// Hashed definitions of the class
@@ -546,7 +557,7 @@ class JSCodeGen()(using genCtx: Context) {
546557
jsSuperClass,
547558
None,
548559
hashedMemberDefs,
549-
Nil)(
560+
topLevelExports)(
550561
OptimizerHints.empty)
551562

552563
classDefinition
@@ -785,10 +796,15 @@ class JSCodeGen()(using genCtx: Context) {
785796
!f.isOneOf(Method | Module) && f.isTerm
786797
&& !f.hasAnnotation(jsdefn.JSNativeAnnot)
787798
&& !f.hasAnnotation(jsdefn.JSOptionalAnnot)
799+
&& !f.hasAnnotation(jsdefn.JSExportStaticAnnot)
788800
}.flatMap({ f =>
789801
implicit val pos = f.span
790802

791-
val isStaticField = f.is(JavaStatic).ensuring(isStatic => !(isStatic && isJSClass))
803+
val isTopLevelExport = f.hasAnnotation(jsdefn.JSExportTopLevelAnnot)
804+
val isJavaStatic = f.is(JavaStatic)
805+
assert(!(isTopLevelExport && isJavaStatic),
806+
em"found ${f.fullName} which is both a top-level export and a Java static")
807+
val isStaticField = isTopLevelExport || isJavaStatic
792808

793809
val namespace = if isStaticField then js.MemberNamespace.PublicStatic else js.MemberNamespace.Public
794810
val mutable = isStaticField || f.is(Mutable)
@@ -797,6 +813,7 @@ class JSCodeGen()(using genCtx: Context) {
797813

798814
val irTpe =
799815
if (isJSClass) genExposedFieldIRType(f)
816+
else if (isTopLevelExport) jstpe.AnyType
800817
else toIRType(f.info)
801818

802819
if (isJSClass && f.isJSExposed)
@@ -806,7 +823,7 @@ class JSCodeGen()(using genCtx: Context) {
806823
val originalName = originalNameOfField(f)
807824
val fieldDef = js.FieldDef(flags, fieldIdent, originalName, irTpe)
808825
val optionalStaticFieldGetter =
809-
if isStaticField then
826+
if isJavaStatic then
810827
// Here we are generating a public static getter for the static field,
811828
// this is its API for other units. This is necessary for singleton
812829
// enum values, which are backed by static fields.
@@ -824,7 +841,7 @@ class JSCodeGen()(using genCtx: Context) {
824841
}).toList
825842
}
826843

827-
private def genExposedFieldIRType(f: Symbol): jstpe.Type = {
844+
def genExposedFieldIRType(f: Symbol): jstpe.Type = {
828845
val tpeEnteringPosterasure = atPhase(elimErasedValueTypePhase)(f.info)
829846
tpeEnteringPosterasure match {
830847
case tpe: ErasedValueType =>
@@ -850,11 +867,11 @@ class JSCodeGen()(using genCtx: Context) {
850867

851868
// Static initializers -----------------------------------------------------
852869

853-
private def genStaticInitializerWithStats(stats: js.Tree)(
870+
private def genStaticConstructorWithStats(name: MethodName, stats: js.Tree)(
854871
implicit pos: Position): js.MethodDef = {
855872
js.MethodDef(
856873
js.MemberFlags.empty.withNamespace(js.MemberNamespace.StaticConstructor),
857-
js.MethodIdent(ir.Names.StaticInitializerName),
874+
js.MethodIdent(name),
858875
NoOriginalName,
859876
Nil,
860877
jstpe.NoType,
@@ -3916,19 +3933,17 @@ class JSCodeGen()(using genCtx: Context) {
39163933
}
39173934

39183935
(f, true)
3919-
} else /*if (jsInterop.topLevelExportsOf(sym).nonEmpty) {
3920-
val f = js.SelectStatic(encodeClassName(sym.owner),
3921-
encodeFieldSym(sym))(jstpe.AnyType)
3936+
} else if (sym.hasAnnotation(jsdefn.JSExportTopLevelAnnot)) {
3937+
val f = js.SelectStatic(encodeClassName(sym.owner), encodeFieldSym(sym))(jstpe.AnyType)
39223938
(f, true)
3923-
} else if (jsInterop.staticExportsOf(sym).nonEmpty) {
3924-
val exportInfo = jsInterop.staticExportsOf(sym).head
3925-
val companionClass = patchedLinkedClassOfClass(sym.owner)
3926-
val f = js.JSSelect(
3927-
genLoadJSConstructor(companionClass),
3928-
js.StringLiteral(exportInfo.jsName))
3929-
3939+
} else if (sym.hasAnnotation(jsdefn.JSExportStaticAnnot)) {
3940+
val jsName = sym.getAnnotation(jsdefn.JSExportStaticAnnot).get.argumentConstantString(0).getOrElse {
3941+
sym.defaultJSName
3942+
}
3943+
val companionClass = sym.owner.linkedClass
3944+
val f = js.JSSelect(genLoadJSConstructor(companionClass), js.StringLiteral(jsName))
39303945
(f, true)
3931-
} else*/ {
3946+
} else {
39323947
val f =
39333948
val className = encodeClassName(sym.owner)
39343949
val fieldIdent = encodeFieldSym(sym)

0 commit comments

Comments
 (0)