@@ -324,13 +324,16 @@ class JSCodeGen()(using genCtx: Context) {
324324
325325 // Optimizer hints
326326
327+ val isDynamicImportThunk = sym.isSubClass(jsdefn.DynamicImportThunkClass )
328+
327329 def isStdLibClassWithAdHocInlineAnnot (sym : Symbol ): Boolean = {
328330 val fullName = sym.fullName.toString
329331 (fullName.startsWith(" scala.Tuple" ) && ! fullName.endsWith(" $" )) ||
330332 (fullName.startsWith(" scala.collection.mutable.ArrayOps$of" ))
331333 }
332334
333335 val shouldMarkInline = (
336+ isDynamicImportThunk ||
334337 sym.hasAnnotation(jsdefn.InlineAnnot ) ||
335338 (sym.isAnonymousFunction && ! sym.isSubClass(defn.PartialFunctionClass )) ||
336339 isStdLibClassWithAdHocInlineAnnot(sym))
@@ -350,14 +353,17 @@ class JSCodeGen()(using genCtx: Context) {
350353 tree match {
351354 case EmptyTree => ()
352355
353- case _ : ValDef =>
354- () // fields are added via genClassFields()
356+ case vd : ValDef =>
357+ // fields are added via genClassFields(), but we need to generate the JS native members
358+ val sym = vd.symbol
359+ if (! sym.is(Module ) && sym.hasAnnotation(jsdefn.JSNativeAnnot ))
360+ generatedNonFieldMembers += genJSNativeMemberDef(vd)
355361
356362 case dd : DefDef =>
357363 val sym = dd.symbol
358-
359- if ( sym.hasAnnotation(jsdefn. JSNativeAnnot ))
360- generatedNonFieldMembers += genJSNativeMemberDef(dd)
364+ if sym.hasAnnotation(jsdefn. JSNativeAnnot ) then
365+ if ! sym.is( Accessor ) then
366+ generatedNonFieldMembers += genJSNativeMemberDef(dd)
361367 else
362368 generatedNonFieldMembers ++= genMethod(dd)
363369
@@ -404,8 +410,12 @@ class JSCodeGen()(using genCtx: Context) {
404410 Nil
405411 }
406412
413+ val optDynamicImportForwarder =
414+ if (isDynamicImportThunk) List (genDynamicImportForwarder(sym))
415+ else Nil
416+
407417 val allMemberDefsExceptStaticForwarders =
408- generatedMembers ::: memberExports ::: optStaticInitializer
418+ generatedMembers ::: memberExports ::: optStaticInitializer ::: optDynamicImportForwarder
409419
410420 // Add static forwarders
411421 val allMemberDefs = if (! isCandidateForForwarders(sym)) {
@@ -1372,12 +1382,12 @@ class JSCodeGen()(using genCtx: Context) {
13721382 // Generate a method -------------------------------------------------------
13731383
13741384 /** Generates the JSNativeMemberDef. */
1375- def genJSNativeMemberDef (tree : DefDef ): js.JSNativeMemberDef = {
1385+ def genJSNativeMemberDef (tree : ValOrDefDef ): js.JSNativeMemberDef = {
13761386 implicit val pos = tree.span
13771387
13781388 val sym = tree.symbol
13791389 val flags = js.MemberFlags .empty.withNamespace(js.MemberNamespace .PublicStatic )
1380- val methodName = encodeMethodSym (sym)
1390+ val methodName = encodeJSNativeMemberSym (sym)
13811391 val jsNativeLoadSpec = computeJSNativeLoadSpecOfValDef(sym)
13821392 js.JSNativeMemberDef (flags, methodName, jsNativeLoadSpec)
13831393 }
@@ -1775,6 +1785,8 @@ class JSCodeGen()(using genCtx: Context) {
17751785 genLoadModule(sym)
17761786 } else if (sym.is(JavaStatic )) {
17771787 genLoadStaticField(sym)
1788+ } else if (sym.hasAnnotation(jsdefn.JSNativeAnnot )) {
1789+ genJSNativeMemberSelect(tree)
17781790 } else {
17791791 val (field, boxed) = genAssignableField(sym, qualifier)
17801792 if (boxed) unbox(field, atPhase(elimErasedValueTypePhase)(sym.info))
@@ -3023,7 +3035,7 @@ class JSCodeGen()(using genCtx: Context) {
30233035 else
30243036 genApplyJSClassMethod(genExpr(receiver), sym, genActualArgs(sym, args))
30253037 } else if (sym.hasAnnotation(jsdefn.JSNativeAnnot )) {
3026- genJSNativeMemberCall(tree, isStat )
3038+ genJSNativeMemberCall(tree)
30273039 } else {
30283040 genApplyMethodMaybeStatically(genExpr(receiver), sym, genActualArgs(sym, args))
30293041 }
@@ -3154,14 +3166,21 @@ class JSCodeGen()(using genCtx: Context) {
31543166 }
31553167
31563168 /** Gen JS code for a call to a native JS def or val. */
3157- private def genJSNativeMemberCall (tree : Apply , isStat : Boolean ): js.Tree = {
3169+ private def genJSNativeMemberSelect (tree : Tree ): js.Tree =
3170+ genJSNativeMemberSelectOrCall(tree, Nil )
3171+
3172+ /** Gen JS code for a call to a native JS def or val. */
3173+ private def genJSNativeMemberCall (tree : Apply ): js.Tree =
3174+ genJSNativeMemberSelectOrCall(tree, tree.args)
3175+
3176+ /** Gen JS code for a call to a native JS def or val. */
3177+ private def genJSNativeMemberSelectOrCall (tree : Tree , args : List [Tree ]): js.Tree = {
31583178 val sym = tree.symbol
3159- val Apply (_, args) = tree
31603179
31613180 implicit val pos = tree.span
31623181
31633182 val jsNativeMemberValue =
3164- js.SelectJSNativeMember (encodeClassName(sym.owner), encodeMethodSym (sym))
3183+ js.SelectJSNativeMember (encodeClassName(sym.owner), encodeJSNativeMemberSym (sym))
31653184
31663185 val boxedResult =
31673186 if (sym.isJSGetter) jsNativeMemberValue
@@ -3497,6 +3516,36 @@ class JSCodeGen()(using genCtx: Context) {
34973516 }
34983517 }
34993518
3519+ /** Generates a static method instantiating and calling this
3520+ * DynamicImportThunk's `apply`:
3521+ *
3522+ * {{{
3523+ * static def dynamicImport$;<params>;Ljava.lang.Object(<params>): any = {
3524+ * new <clsSym>.<init>;<params>:V(<params>).apply;Ljava.lang.Object()
3525+ * }
3526+ * }}}
3527+ */
3528+ private def genDynamicImportForwarder (clsSym : Symbol )(using Position ): js.MethodDef = {
3529+ withNewLocalNameScope {
3530+ val ctor = clsSym.primaryConstructor
3531+ val paramSyms = ctor.paramSymss.flatten
3532+ val paramDefs = paramSyms.map(genParamDef(_))
3533+
3534+ val body = {
3535+ val inst = js.New (encodeClassName(clsSym), encodeMethodSym(ctor), paramDefs.map(_.ref))
3536+ genApplyMethod(inst, jsdefn.DynamicImportThunkClass_apply , Nil )
3537+ }
3538+
3539+ js.MethodDef (
3540+ js.MemberFlags .empty.withNamespace(js.MemberNamespace .PublicStatic ),
3541+ encodeDynamicImportForwarderIdent(paramSyms),
3542+ NoOriginalName ,
3543+ paramDefs,
3544+ jstpe.AnyType ,
3545+ Some (body))(OptimizerHints .empty, None )
3546+ }
3547+ }
3548+
35003549 /** Boxes a value of the given type before `elimErasedValueType`.
35013550 *
35023551 * This should be used when sending values to a JavaScript context, which
@@ -3800,6 +3849,46 @@ class JSCodeGen()(using genCtx: Context) {
38003849 // js.import.meta
38013850 js.JSImportMeta ()
38023851
3852+ case DYNAMIC_IMPORT =>
3853+ // runtime.dynamicImport
3854+ assert(args.size == 1 ,
3855+ s " Expected exactly 1 argument for JS primitive $code but got " +
3856+ s " ${args.size} at $pos" )
3857+
3858+ args.head match {
3859+ case Block (stats, expr @ Typed (Apply (fun @ Select (New (tpt), _), args), _)) =>
3860+ /* stats is always empty if no other compiler plugin is present.
3861+ * However, code instrumentation (notably scoverage) might add
3862+ * statements here. If this is the case, the thunk anonymous class
3863+ * has already been created when the other plugin runs (i.e. the
3864+ * plugin ran after jsinterop).
3865+ *
3866+ * Therefore, it is OK to leave the statements on our side of the
3867+ * dynamic loading boundary.
3868+ */
3869+
3870+ val clsSym = tpt.symbol
3871+ val ctor = fun.symbol
3872+
3873+ assert(clsSym.isSubClass(jsdefn.DynamicImportThunkClass ),
3874+ s " expected subclass of DynamicImportThunk, got: $clsSym at: ${expr.sourcePos}" )
3875+ assert(ctor.isPrimaryConstructor,
3876+ s " expected primary constructor, got: $ctor at: ${expr.sourcePos}" )
3877+
3878+ js.Block (
3879+ stats.map(genStat(_)),
3880+ js.ApplyDynamicImport (
3881+ js.ApplyFlags .empty,
3882+ encodeClassName(clsSym),
3883+ encodeDynamicImportForwarderIdent(ctor.paramSymss.flatten),
3884+ genActualArgs(ctor, args))
3885+ )
3886+
3887+ case tree =>
3888+ throw new FatalError (
3889+ s " Unexpected argument tree in dynamicImport: $tree/ ${tree.getClass} at: $pos" )
3890+ }
3891+
38033892 case JS_NATIVE =>
38043893 // js.native
38053894 report.error(
0 commit comments