Skip to content

Commit 5f9c128

Browse files
committed
Fix #4526: Find module accessor of nested private companions
1 parent 4d41219 commit 5f9c128

File tree

2 files changed

+78
-34
lines changed

2 files changed

+78
-34
lines changed

compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala

Lines changed: 63 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -697,24 +697,78 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {
697697
previousArgsValues: Int => List[js.Tree])(
698698
implicit pos: Position): js.Tree = {
699699

700-
val trgSym = {
701-
if (sym.isClassConstructor) {
702-
/* Get the companion module class.
703-
* For inner classes the sym.owner.companionModule can be broken,
704-
* therefore companionModule is fetched at uncurryPhase.
700+
val owner = sym.owner
701+
val isNested = owner.isLifted && !isStaticModule(owner.originalOwner)
702+
703+
val (trgSym, trgTree) = {
704+
if (!sym.isClassConstructor && !static) {
705+
/* Default getter is on the receiver.
706+
*
707+
* Since we only use this method for class internal exports
708+
* dispatchers, we know the default getter is on `this`. This applies
709+
* to both top-level and nested classes.
710+
*/
711+
(owner, js.This()(encodeClassType(owner)))
712+
} else if (isNested) {
713+
assert(captures.size == 1,
714+
s"expected exactly one capture got $captures ($sym at $pos)")
715+
716+
/* Find the module accessor.
705717
*
706718
* #4465: If owner is a nested class, the linked class is *not* a
707719
* module value, but another class. In this case we need to call the
708720
* module accessor on the enclosing class to retrieve this.
721+
*
722+
* #4526: If the companion module is private, linkedClassOfClass
723+
* does not work (because the module name is prefixed with the full
724+
* path). So we find the module accessor first and take its result
725+
* type to be the companion module type.
726+
*/
727+
val outer = owner.originalOwner
728+
729+
val modAccessor = {
730+
val name = enteringPhase(currentRun.typerPhase) {
731+
owner.unexpandedName.toTermName
732+
}
733+
734+
outer.info.members.find { sym =>
735+
sym.isModule && sym.unexpandedName == name
736+
}.getOrElse {
737+
throw new AssertionError(
738+
s"couldn't find module accessor for ${owner.fullName} at $pos")
739+
}
740+
}
741+
742+
val receiver = captures.head
743+
744+
val trgSym = modAccessor.tpe.resultType.typeSymbol
745+
746+
val trgTree = if (isJSType(outer)) {
747+
genApplyJSClassMethod(receiver, modAccessor, Nil)
748+
} else {
749+
genApplyMethodMaybeStatically(receiver, modAccessor, Nil)
750+
}
751+
752+
(trgSym, trgTree)
753+
} else if (sym.isClassConstructor) {
754+
assert(captures.isEmpty, "expected empty captures")
755+
756+
/* Get the companion module class.
757+
* For classes nested inside modules the sym.owner.companionModule
758+
* can be broken, therefore companionModule is fetched at
759+
* uncurryPhase.
709760
*/
710-
val companionModule = enteringPhase(currentRun.uncurryPhase) {
711-
sym.owner.companionModule
761+
val trgSym = enteringPhase(currentRun.uncurryPhase) {
762+
owner.linkedClassOfClass
712763
}
713-
companionModule.moduleClass
764+
(trgSym, genLoadModule(trgSym))
714765
} else {
715-
sym.owner
766+
assert(static, "expected static")
767+
assert(captures.isEmpty, "expected empty captures")
768+
(owner, genLoadModule(owner))
716769
}
717770
}
771+
718772
val defaultGetter = trgSym.tpe.member(
719773
nme.defaultGetterName(sym.name, paramIndex + 1))
720774

@@ -723,31 +777,6 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {
723777
assert(!defaultGetter.isOverloaded,
724778
s"found overloaded default getter $defaultGetter")
725779

726-
val trgTree = {
727-
if (sym.isClassConstructor || static) {
728-
if (!trgSym.isLifted) {
729-
assert(captures.isEmpty, "expected empty captures")
730-
genLoadModule(trgSym)
731-
} else {
732-
assert(captures.size == 1, "expected exactly one capture")
733-
734-
// Find the module accessor.
735-
val outer = trgSym.originalOwner
736-
val name = enteringPhase(currentRun.typerPhase)(trgSym.unexpandedName)
737-
738-
val modAccessor = outer.info.members.lookupModule(name)
739-
val receiver = captures.head
740-
if (isJSType(outer)) {
741-
genApplyJSClassMethod(receiver, modAccessor, Nil)
742-
} else {
743-
genApplyMethodMaybeStatically(receiver, modAccessor, Nil)
744-
}
745-
}
746-
} else {
747-
js.This()(encodeClassType(trgSym))
748-
}
749-
}
750-
751780
// Pass previous arguments to defaultGetter
752781
val defaultGetterArgs = previousArgsValues(defaultGetter.tpe.params.size)
753782

test-suite/js/src/test/scala/org/scalajs/testsuite/jsinterop/NestedJSClassTest.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,13 @@ class NestedJSClassTest {
469469
assertEquals(1, container.moduleSideEffect)
470470
}
471471

472+
@Test def defaultCtorParamsInnerJSClassPrivateCompanion_Issue4526(): Unit = {
473+
val container = new ScalaClassContainer("container")
474+
475+
val inner = new container.InnerJSClassDefaultParamsPrivateCompanion_Issue4526()
476+
assertEquals("container inner foo", inner.foo())
477+
}
478+
472479
@Test def doublyNestedInnerObject_Issue4114(): Unit = {
473480
val outer1 = new DoublyNestedInnerObject_Issue4114().asInstanceOf[js.Dynamic]
474481
val outer2 = new DoublyNestedInnerObject_Issue4114().asInstanceOf[js.Dynamic]
@@ -696,6 +703,14 @@ object NestedJSClassTest {
696703
object InnerJSClassDefaultParams_Issue4465 {
697704
moduleSideEffect += 1
698705
}
706+
707+
class InnerJSClassDefaultParamsPrivateCompanion_Issue4526(
708+
withDefault: String = "inner") extends js.Object {
709+
def foo(methodDefault: String = "foo"): String =
710+
s"$xxx $withDefault $methodDefault"
711+
}
712+
713+
private object InnerJSClassDefaultParamsPrivateCompanion_Issue4526
699714
}
700715

701716
trait ScalaTraitContainer {

0 commit comments

Comments
 (0)