Skip to content

Commit 1b9bdc2

Browse files
committed
Move the computation of JS native load specs to the back-end.
This allows to remove `SJSPlatform.perRunInfo`. We still check that they are valid during `PrepJSInterop`.
1 parent 6f6f710 commit 1b9bdc2

File tree

4 files changed

+104
-93
lines changed

4 files changed

+104
-93
lines changed

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

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,7 @@ class JSCodeGen()(using genCtx: Context) {
464464
val superClass =
465465
if (sym.is(Trait)) None
466466
else Some(encodeClassNameIdent(sym.superClass))
467-
val jsNativeLoadSpec = sjsPlatform.perRunInfo.jsNativeLoadSpecOfOption(sym)
467+
val jsNativeLoadSpec = computeJSNativeLoadSpecOfClass(sym)
468468

469469
js.ClassDef(
470470
classIdent,
@@ -3497,6 +3497,84 @@ class JSCodeGen()(using genCtx: Context) {
34973497
}
34983498
}
34993499

3500+
private def computeJSNativeLoadSpecOfClass(sym: Symbol): Option[js.JSNativeLoadSpec] = {
3501+
if (sym.is(Trait) || sym.hasAnnotation(jsdefn.JSGlobalScopeAnnot)) {
3502+
None
3503+
} else {
3504+
atPhase(picklerPhase.next) {
3505+
if (sym.owner.isStaticOwner)
3506+
Some(computeJSNativeLoadSpecOfInPhase(sym))
3507+
else
3508+
None
3509+
}
3510+
}
3511+
}
3512+
3513+
private def computeJSNativeLoadSpecOfInPhase(sym: Symbol)(using Context): js.JSNativeLoadSpec = {
3514+
import js.JSNativeLoadSpec._
3515+
3516+
val symOwner = sym.owner
3517+
3518+
// Marks a code path as unexpected because it should have been reported as an error in `PrepJSInterop`.
3519+
def unexpected(msg: String): Nothing =
3520+
throw new FatalError(i"$msg for ${sym.fullName} at ${sym.srcPos}")
3521+
3522+
if (symOwner.hasAnnotation(jsdefn.JSNativeAnnot)) {
3523+
val jsName = sym.jsName match {
3524+
case JSName.Literal(jsName) => jsName
3525+
case JSName.Computed(_) => unexpected("could not read the simple JS name as a string literal")
3526+
}
3527+
3528+
if (symOwner.hasAnnotation(jsdefn.JSGlobalScopeAnnot)) {
3529+
Global(jsName, Nil)
3530+
} else {
3531+
val ownerLoadSpec = computeJSNativeLoadSpecOfInPhase(symOwner)
3532+
ownerLoadSpec match {
3533+
case Global(globalRef, path) =>
3534+
Global(globalRef, path :+ jsName)
3535+
case Import(module, path) =>
3536+
Import(module, path :+ jsName)
3537+
case ImportWithGlobalFallback(Import(module, modulePath), Global(globalRef, globalPath)) =>
3538+
ImportWithGlobalFallback(
3539+
Import(module, modulePath :+ jsName),
3540+
Global(globalRef, globalPath :+ jsName))
3541+
}
3542+
}
3543+
} else {
3544+
def parsePath(pathName: String): List[String] =
3545+
pathName.split('.').toList
3546+
3547+
def parseGlobalPath(pathName: String): Global = {
3548+
val globalRef :: path = parsePath(pathName)
3549+
Global(globalRef, path)
3550+
}
3551+
3552+
val annot = sym.annotations.find { annot =>
3553+
annot.symbol == jsdefn.JSGlobalAnnot || annot.symbol == jsdefn.JSImportAnnot
3554+
}.getOrElse {
3555+
unexpected("could not find the JS native load spec annotation")
3556+
}
3557+
3558+
if (annot.symbol == jsdefn.JSGlobalAnnot) {
3559+
val pathName = annot.argumentConstantString(0).getOrElse {
3560+
sym.defaultJSName
3561+
}
3562+
parseGlobalPath(pathName)
3563+
} else { // annot.symbol == jsdefn.JSImportAnnot
3564+
val module = annot.argumentConstantString(0).getOrElse {
3565+
unexpected("could not read the module argument as a string literal")
3566+
}
3567+
val path = annot.argumentConstantString(1).fold[List[String]](Nil)(parsePath)
3568+
val importSpec = Import(module, path)
3569+
annot.argumentConstantString(2).fold[js.JSNativeLoadSpec] {
3570+
importSpec
3571+
} { globalPathName =>
3572+
ImportWithGlobalFallback(importSpec, parseGlobalPath(globalPathName))
3573+
}
3574+
}
3575+
}
3576+
}
3577+
35003578
private def isMethodStaticInIR(sym: Symbol): Boolean =
35013579
sym.is(JavaStatic)
35023580

compiler/src/dotty/tools/dotc/config/SJSPlatform.scala

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,4 @@ class SJSPlatform()(using Context) extends JavaPlatform {
2828

2929
override def shouldReceiveJavaSerializationMethods(sym: ClassSymbol)(using Context): Boolean =
3030
!sym.isSubClass(jsDefinitions.JSAnyClass)
31-
32-
object perRunInfo {
33-
private val jsNativeLoadSpecs = new MutableSymbolMap[JSNativeLoadSpec]
34-
35-
/** Clears all the info at the beginning of a run. */
36-
def clear(): Unit =
37-
jsNativeLoadSpecs.clear()
38-
39-
/** Stores the JS native load spec of a symbol for the current compilation run. */
40-
def storeJSNativeLoadSpec(sym: Symbol, spec: JSNativeLoadSpec): Unit =
41-
jsNativeLoadSpecs(sym) = spec
42-
43-
/** Gets the JS native load spec of a symbol in the current compilation run. */
44-
def jsNativeLoadSpecOf(sym: Symbol): JSNativeLoadSpec =
45-
jsNativeLoadSpecs(sym)
46-
47-
/** Gets the JS native load spec of a symbol in the current compilation run, if it has one. */
48-
def jsNativeLoadSpecOfOption(sym: Symbol): Option[JSNativeLoadSpec] =
49-
jsNativeLoadSpecs.get(sym)
50-
}
5131
}

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

Lines changed: 24 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,6 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP
6969

7070
override def changesMembers: Boolean = true // the phase adds export forwarders
7171

72-
override def runOn(units: List[CompilationUnit])(using Context): List[CompilationUnit] = {
73-
sjsPlatform.perRunInfo.clear()
74-
super.runOn(units)
75-
}
76-
7772
protected def newTransformer(using Context): Transformer =
7873
new ScalaJSPrepJSInteropTransformer
7974

@@ -544,29 +539,17 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP
544539
report.error(i"Traits may not have an @${annotSym.name} annotation.", annot.tree)
545540
}
546541
} else {
547-
/* Compute the loading spec now, before `flatten` destroys the name.
548-
* We store it in a global map.
549-
*/
550-
val optLoadSpec = checkAndComputeJSNativeLoadSpecOf(treePos, sym)
551-
for (loadSpec <- optLoadSpec)
552-
sjsPlatform.perRunInfo.storeJSNativeLoadSpec(sym, loadSpec)
542+
checkJSNativeLoadSpecOf(treePos, sym)
553543
}
554544
}
555545
}
556546

557-
private def checkAndComputeJSNativeLoadSpecOf(pos: SrcPos, sym: Symbol)(
558-
using Context): Option[JSNativeLoadSpec] = {
547+
private def checkJSNativeLoadSpecOf(pos: SrcPos, sym: Symbol)(using Context): Unit = {
559548
import JSNativeLoadSpec._
560549

561-
def makeGlobalRefNativeLoadSpec(globalRef: String,
562-
path: List[String]): Global = {
563-
val validatedGlobalRef = if (!JSGlobalRef.isValidJSGlobalRefName(globalRef)) {
550+
def checkGlobalRefName(globalRef: String): Unit = {
551+
if (!JSGlobalRef.isValidJSGlobalRefName(globalRef))
564552
report.error(s"The name of a JS global variable must be a valid JS identifier (got '$globalRef')", pos)
565-
"erroneous"
566-
} else {
567-
globalRef
568-
}
569-
Global(validatedGlobalRef, path)
570553
}
571554

572555
if (enclosingOwner is OwnerKind.JSNative) {
@@ -593,86 +576,56 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP
593576
}
594577
}
595578

596-
val jsName = sym.jsName match {
597-
case JSName.Literal(jsName) => jsName
598-
case JSName.Computed(_) => "<erroneous>" // compile error above
599-
}
600-
601-
val ownerLoadSpec = sjsPlatform.perRunInfo.jsNativeLoadSpecOfOption(sym.owner)
602-
val loadSpec = ownerLoadSpec match {
603-
case None =>
604-
// The owner is a JSGlobalScope
605-
makeGlobalRefNativeLoadSpec(jsName, Nil)
606-
case Some(Global(globalRef, path)) =>
607-
Global(globalRef, path :+ jsName)
608-
case Some(Import(module, path)) =>
609-
Import(module, path :+ jsName)
610-
case Some(ImportWithGlobalFallback(
611-
Import(module, modulePath), Global(globalRef, globalPath))) =>
612-
ImportWithGlobalFallback(
613-
Import(module, modulePath :+ jsName),
614-
Global(globalRef, globalPath :+ jsName))
579+
if (sym.owner.hasAnnotation(jsdefn.JSGlobalScopeAnnot)) {
580+
val jsName = sym.jsName match {
581+
case JSName.Literal(jsName) =>
582+
checkGlobalRefName(jsName)
583+
case JSName.Computed(_) =>
584+
() // compile error above
585+
}
615586
}
616-
Some(loadSpec)
617-
} else {
618-
None
619587
}
620588
} else {
621-
def parsePath(pathName: String): List[String] =
622-
pathName.split('.').toList
623-
624-
def parseGlobalPath(pathName: String): Global = {
625-
val globalRef :: path = parsePath(pathName)
626-
makeGlobalRefNativeLoadSpec(globalRef, path)
589+
def firstElementOfPath(pathName: String): String = {
590+
val dotIndex = pathName.indexOf('.')
591+
if (dotIndex < 0) pathName
592+
else pathName.substring(0, dotIndex)
627593
}
628594

595+
def checkGlobalRefPath(pathName: String): Unit =
596+
checkGlobalRefName(firstElementOfPath(pathName))
597+
629598
checkAndGetJSNativeLoadingSpecAnnotOf(pos, sym) match {
630599
case Some(annot) if annot.symbol == jsdefn.JSGlobalScopeAnnot =>
631600
if (!sym.is(Module)) {
632601
report.error(
633602
"@JSGlobalScope can only be used on native JS objects (with @js.native).",
634603
annot.tree)
635604
}
636-
None
637605

638606
case Some(annot) if annot.symbol == jsdefn.JSGlobalAnnot =>
639607
if (shouldCheckLiterals)
640608
checkJSGlobalLiteral(annot)
641609
val pathName = annot.argumentConstantString(0).getOrElse {
642-
val needsExplicitJSName = {
643-
(enclosingOwner is OwnerKind.ScalaMod) &&
644-
!sym.owner.isPackageObject
645-
}
646-
647-
if (needsExplicitJSName) {
610+
if ((enclosingOwner is OwnerKind.ScalaMod) && !sym.owner.isPackageObject) {
648611
report.error(
649612
"Native JS members inside non-native objects must have an explicit name in @JSGlobal",
650613
annot.tree)
651614
}
652615
sym.defaultJSName
653616
}
654-
Some(parseGlobalPath(pathName))
617+
checkGlobalRefPath(pathName)
655618

656619
case Some(annot) if annot.symbol == jsdefn.JSImportAnnot =>
657620
if (shouldCheckLiterals)
658621
checkJSImportLiteral(annot)
659-
val module = annot.argumentConstantString(0).getOrElse {
660-
"" // an error is reported by checkJSImportLiteral in this case
661-
}
662-
val path = annot.argumentConstantString(1).fold[List[String]](Nil)(parsePath)
663-
val importSpec = Import(module, path)
664-
val loadSpec = annot.argumentConstantString(2).fold[JSNativeLoadSpec] {
665-
importSpec
666-
} { globalPathName =>
667-
ImportWithGlobalFallback(importSpec, parseGlobalPath(globalPathName))
622+
annot.argumentConstantString(2).foreach { globalPathName =>
623+
checkGlobalRefPath(globalPathName)
668624
}
669-
Some(loadSpec)
670625

671626
case _ =>
672-
/* We already emitted an error. Invent something not to cause
673-
* cascading errors.
674-
*/
675-
Some(JSNativeLoadSpec.Global("erroneous", Nil))
627+
// We already emitted an error in checkAndGetJSNativeLoadingSpecAnnotOf
628+
()
676629
}
677630
}
678631
}

project/Build.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1049,7 +1049,7 @@ object Build {
10491049
-- "ExportsTest.scala" // JS exports
10501050
-- "IterableTest.scala" // non-native JS classes
10511051
-- "JSExportStaticTest.scala" // JS exports
1052-
-- "JSNativeInPackage.scala" // tests fail (wrong load spec for JS globals)
1052+
-- "JSNativeInPackage.scala" // #9785 tests fail due to js.typeOf(globalVar) being incorrect
10531053
-- "JSOptionalTest.scala" // non-native JS classes
10541054
-- "JSSymbolTest.scala" // non-native JS classes
10551055
-- "MiscInteropTest.scala" // non-native JS classes

0 commit comments

Comments
 (0)