diff --git a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala index 5b0d37d28a9b..8328afd52573 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala @@ -31,6 +31,7 @@ import org.scalajs.ir.Names.{ClassName, MethodName, SimpleMethodName} import org.scalajs.ir.OriginalName import org.scalajs.ir.OriginalName.NoOriginalName import org.scalajs.ir.Trees.OptimizerHints +import org.scalajs.ir.Version.Unversioned import dotty.tools.dotc.transform.sjs.JSSymUtils.* @@ -354,7 +355,8 @@ class JSCodeGen()(using genCtx: Context) { // Generate members (constructor + methods) - val generatedNonFieldMembers = new mutable.ListBuffer[js.MemberDef] + val methodsBuilder = List.newBuilder[js.MethodDef] + val jsNativeMembersBuilder = List.newBuilder[js.JSNativeMemberDef] val tpl = td.rhs.asInstanceOf[Template] for (tree <- tpl.constr :: tpl.body) { @@ -365,23 +367,25 @@ class JSCodeGen()(using genCtx: Context) { // fields are added via genClassFields(), but we need to generate the JS native members val sym = vd.symbol if (!sym.is(Module) && sym.hasAnnotation(jsdefn.JSNativeAnnot)) - generatedNonFieldMembers += genJSNativeMemberDef(vd) + jsNativeMembersBuilder += genJSNativeMemberDef(vd) case dd: DefDef => val sym = dd.symbol if sym.hasAnnotation(jsdefn.JSNativeAnnot) then if !sym.is(Accessor) then - generatedNonFieldMembers += genJSNativeMemberDef(dd) + jsNativeMembersBuilder += genJSNativeMemberDef(dd) else - generatedNonFieldMembers ++= genMethod(dd) + methodsBuilder ++= genMethod(dd) case _ => throw new FatalError("Illegal tree in body of genScalaClass(): " + tree) } } - // Generate fields and add to methods + ctors - val generatedMembers = genClassFields(td) ++ generatedNonFieldMembers.toList + val (fields, staticGetterDefs) = if (!isHijacked) genClassFields(td) else (Nil, Nil) + + val jsNativeMembers = jsNativeMembersBuilder.result() + val generatedMethods = methodsBuilder.result() ::: staticGetterDefs // Generate member exports val memberExports = jsExportsGen.genMemberExports(sym) @@ -422,12 +426,12 @@ class JSCodeGen()(using genCtx: Context) { if (isDynamicImportThunk) List(genDynamicImportForwarder(sym)) else Nil - val allMemberDefsExceptStaticForwarders = - generatedMembers ::: memberExports ::: optStaticInitializer ::: optDynamicImportForwarder + val allMethodsExceptStaticForwarders: List[js.MethodDef] = + generatedMethods ::: optStaticInitializer ::: optDynamicImportForwarder // Add static forwarders - val allMemberDefs = if (!isCandidateForForwarders(sym)) { - allMemberDefsExceptStaticForwarders + val allMethods = if (!isCandidateForForwarders(sym)) { + allMethodsExceptStaticForwarders } else { if (isStaticModule(sym)) { /* If the module class has no linked class, we must create one to @@ -446,23 +450,24 @@ class JSCodeGen()(using genCtx: Context) { Nil, None, None, - forwarders, - Nil + fields = Nil, + methods = forwarders, + jsConstructor = None, + jsMethodProps = Nil, + jsNativeMembers = Nil, + topLevelExportDefs = Nil )(js.OptimizerHints.empty) generatedStaticForwarderClasses += sym -> forwardersClassDef } } - allMemberDefsExceptStaticForwarders + allMethodsExceptStaticForwarders } else { val forwarders = genStaticForwardersForClassOrInterface( - allMemberDefsExceptStaticForwarders, sym) - allMemberDefsExceptStaticForwarders ::: forwarders + allMethodsExceptStaticForwarders, sym) + allMethodsExceptStaticForwarders ::: forwarders } } - // Hashed definitions of the class - val hashedDefs = ir.Hashers.hashMemberDefs(allMemberDefs) - // The complete class definition val kind = if (isStaticModule(sym)) ClassKind.ModuleClass @@ -478,11 +483,15 @@ class JSCodeGen()(using genCtx: Context) { genClassInterfaces(sym, forJSClass = false), None, None, - hashedDefs, + fields, + allMethods, + jsConstructor = None, + memberExports, + jsNativeMembers, topLevelExportDefs)( optimizerHints) - classDefinition + ir.Hashers.hashClassDef(classDefinition) } /** Gen the IR ClassDef for a Scala.js-defined JS class. */ @@ -546,22 +555,22 @@ class JSCodeGen()(using genCtx: Context) { } // Static members (exported from the companion object) - val staticMembers = { + val (staticFields, staticExports) = { val module = sym.companionModule if (!module.exists) { - Nil + (Nil, Nil) } else { val companionModuleClass = module.moduleClass - val exports = withScopedVars(currentClassSym := companionModuleClass) { + val (staticFields, staticExports) = withScopedVars(currentClassSym := companionModuleClass) { jsExportsGen.genStaticExports(companionModuleClass) } - if (exports.exists(_.isInstanceOf[js.JSFieldDef])) { - val classInitializer = + + if (staticFields.nonEmpty) { + generatedMethods += genStaticConstructorWithStats(ir.Names.ClassInitializerName, genLoadModule(companionModuleClass)) - exports :+ classInitializer - } else { - exports } + + (staticFields, staticExports) } } @@ -587,17 +596,12 @@ class JSCodeGen()(using genCtx: Context) { (ctor, jsClassCaptures) } - // Generate fields (and add to methods + ctors) - val generatedMembers = { - genClassFields(td) ::: - generatedConstructor :: - jsExportsGen.genJSClassDispatchers(sym, dispatchMethodNames.result().distinct) ::: - generatedMethods.toList ::: - staticMembers - } + // Generate fields + val (fields, staticGetterDefs) = genClassFields(td) - // Hashed definitions of the class - val hashedMemberDefs = ir.Hashers.hashMemberDefs(generatedMembers) + val methods = generatedMethods.toList ::: staticGetterDefs + val jsMethodProps = + jsExportsGen.genJSClassDispatchers(sym, dispatchMethodNames.result().distinct) ::: staticExports // The complete class definition val kind = @@ -613,11 +617,15 @@ class JSCodeGen()(using genCtx: Context) { genClassInterfaces(sym, forJSClass = true), jsSuperClass = jsClassCaptures.map(_.head.ref), None, - hashedMemberDefs, + fields ::: staticFields, + methods, + Some(generatedConstructor), + jsMethodProps, + jsNativeMembers = Nil, topLevelExports)( OptimizerHints.empty) - classDefinition + ir.Hashers.hashClassDef(classDefinition) } /** Gen the IR ClassDef for a raw JS class or trait. @@ -647,6 +655,10 @@ class JSCodeGen()(using genCtx: Context) { None, jsNativeLoadSpec, Nil, + Nil, + None, + Nil, + Nil, Nil)( OptimizerHints.empty) } @@ -681,10 +693,7 @@ class JSCodeGen()(using genCtx: Context) { if (!isCandidateForForwarders(sym)) genMethodsList else genMethodsList ::: genStaticForwardersForClassOrInterface(genMethodsList, sym) - // Hashed definitions of the interface - val hashedDefs = ir.Hashers.hashMemberDefs(allMemberDefs) - - js.ClassDef( + val classDef = js.ClassDef( classIdent, originalNameOfClass(sym), ClassKind.Interface, @@ -693,9 +702,15 @@ class JSCodeGen()(using genCtx: Context) { superInterfaces, None, None, - hashedDefs, + Nil, + allMemberDefs, + None, + Nil, + Nil, Nil)( OptimizerHints.empty) + + ir.Hashers.hashClassDef(classDef) } private def genClassInterfaces(sym: ClassSymbol, forJSClass: Boolean)( @@ -763,15 +778,15 @@ class JSCodeGen()(using genCtx: Context) { * Precondition: `isCandidateForForwarders(sym)` is true */ def genStaticForwardersForClassOrInterface( - existingMembers: List[js.MemberDef], sym: Symbol)( - implicit pos: SourcePosition): List[js.MemberDef] = { + existingMethods: List[js.MethodDef], sym: Symbol)( + implicit pos: SourcePosition): List[js.MethodDef] = { val module = sym.companionModule if (!module.exists) { Nil } else { val moduleClass = module.moduleClass if (!moduleClass.isJSType) - genStaticForwardersFromModuleClass(existingMembers, moduleClass) + genStaticForwardersFromModuleClass(existingMethods, moduleClass) else Nil } @@ -781,13 +796,13 @@ class JSCodeGen()(using genCtx: Context) { * * Precondition: `isCandidateForForwarders(moduleClass)` is true */ - def genStaticForwardersFromModuleClass(existingMembers: List[js.MemberDef], + def genStaticForwardersFromModuleClass(existingMethods: List[js.MethodDef], moduleClass: Symbol)( - implicit pos: SourcePosition): List[js.MemberDef] = { + implicit pos: SourcePosition): List[js.MethodDef] = { assert(moduleClass.is(ModuleClass), moduleClass) - val existingPublicStaticMethodNames = existingMembers.collect { + val existingPublicStaticMethodNames = existingMethods.collect { case js.MethodDef(flags, name, _, _, _, _) if flags.namespace == js.MemberNamespace.PublicStatic => name.name @@ -849,7 +864,7 @@ class JSCodeGen()(using genCtx: Context) { js.MethodDef(flags, methodIdent, originalName, jsParams, resultType, Some { genApplyMethod(genLoadModule(moduleClass), m, jsParams.map(_.ref)) - })(OptimizerHints.empty, None) + })(OptimizerHints.empty, Unversioned) } } @@ -859,20 +874,23 @@ class JSCodeGen()(using genCtx: Context) { // Generate the fields of a class ------------------------------------------ /** Gen definitions for the fields of a class. */ - private def genClassFields(td: TypeDef): List[js.MemberDef] = { + private def genClassFields(td: TypeDef): (List[js.AnyFieldDef], List[js.MethodDef]) = { val classSym = td.symbol.asClass assert(currentClassSym.get == classSym, "genClassFields called with a ClassDef other than the current one") val isJSClass = classSym.isNonNativeJSClass + val fieldDefs = List.newBuilder[js.AnyFieldDef] + val staticGetterDefs = List.newBuilder[js.MethodDef] + // Term members that are neither methods nor modules are fields classSym.info.decls.filter { f => !f.isOneOf(MethodOrModule) && f.isTerm && !f.hasAnnotation(jsdefn.JSNativeAnnot) && !f.hasAnnotation(jsdefn.JSOptionalAnnot) && !f.hasAnnotation(jsdefn.JSExportStaticAnnot) - }.flatMap({ f => + }.foreach { f => implicit val pos = f.span val isTopLevelExport = f.hasAnnotation(jsdefn.JSExportTopLevelAnnot) @@ -897,28 +915,27 @@ class JSCodeGen()(using genCtx: Context) { else irTpe0 if (isJSClass && f.isJSExposed) - js.JSFieldDef(flags, genExpr(f.jsName)(f.sourcePos), irTpe) :: Nil + fieldDefs += js.JSFieldDef(flags, genExpr(f.jsName)(f.sourcePos), irTpe) else val fieldIdent = encodeFieldSym(f) val originalName = originalNameOfField(f) - val fieldDef = js.FieldDef(flags, fieldIdent, originalName, irTpe) - val optionalStaticFieldGetter = - if isJavaStatic then - // Here we are generating a public static getter for the static field, - // this is its API for other units. This is necessary for singleton - // enum values, which are backed by static fields. - val className = encodeClassName(classSym) - val body = js.Block( - js.LoadModule(className), - js.SelectStatic(className, fieldIdent)(irTpe)) - js.MethodDef(js.MemberFlags.empty.withNamespace(js.MemberNamespace.PublicStatic), - encodeStaticMemberSym(f), originalName, Nil, irTpe, - Some(body))( - OptimizerHints.empty, None) :: Nil - else - Nil - fieldDef :: optionalStaticFieldGetter - }).toList + fieldDefs += js.FieldDef(flags, fieldIdent, originalName, irTpe) + if isJavaStatic then + // Here we are generating a public static getter for the static field, + // this is its API for other units. This is necessary for singleton + // enum values, which are backed by static fields. + val className = encodeClassName(classSym) + val body = js.Block( + js.LoadModule(className), + js.SelectStatic(className, fieldIdent)(irTpe)) + staticGetterDefs += js.MethodDef( + js.MemberFlags.empty.withNamespace(js.MemberNamespace.PublicStatic), + encodeStaticMemberSym(f), originalName, Nil, irTpe, + Some(body))( + OptimizerHints.empty, Unversioned) + } + + (fieldDefs.result(), staticGetterDefs.result()) } def genExposedFieldIRType(f: Symbol): jstpe.Type = { @@ -956,7 +973,7 @@ class JSCodeGen()(using genCtx: Context) { Nil, jstpe.NoType, Some(stats))( - OptimizerHints.empty, None) + OptimizerHints.empty, Unversioned) } private def genRegisterReflectiveInstantiation(sym: Symbol)( @@ -1122,7 +1139,7 @@ class JSCodeGen()(using genCtx: Context) { val constructorDef = js.JSConstructorDef( js.MemberFlags.empty.withNamespace(js.MemberNamespace.Constructor), - formalArgs, restParam, constructorBody)(OptimizerHints.empty, None) + formalArgs, restParam, constructorBody)(OptimizerHints.empty, Unversioned) (jsClassCaptures, constructorDef) } @@ -1504,7 +1521,7 @@ class JSCodeGen()(using genCtx: Context) { } else if (sym.is(Deferred)) { Some(js.MethodDef(js.MemberFlags.empty, methodName, originalName, jsParams, toIRType(patchedResultType(sym)), None)( - OptimizerHints.empty, None)) + OptimizerHints.empty, Unversioned)) } else if (isIgnorableDefaultParam) { // #11592 None @@ -1545,7 +1562,7 @@ class JSCodeGen()(using genCtx: Context) { val namespace = js.MemberNamespace.Constructor js.MethodDef(js.MemberFlags.empty.withNamespace(namespace), methodName, originalName, jsParams, jstpe.NoType, Some(genStat(rhs)))( - optimizerHints, None) + optimizerHints, Unversioned) } else { val namespace = if (isMethodStaticInIR(sym)) { if (sym.isPrivate) js.MemberNamespace.PrivateStatic @@ -1590,7 +1607,7 @@ class JSCodeGen()(using genCtx: Context) { if (namespace.isStatic || !currentClassSym.isNonNativeJSClass) { val flags = js.MemberFlags.empty.withNamespace(namespace) js.MethodDef(flags, methodName, originalName, jsParams, resultIRType, Some(genBody()))( - optimizerHints, None) + optimizerHints, Unversioned) } else { val thisLocalIdent = freshLocalIdent("this") withScopedVars( @@ -1606,7 +1623,7 @@ class JSCodeGen()(using genCtx: Context) { js.MethodDef(flags, methodName, originalName, thisParamDef :: jsParams, resultIRType, Some(genBody()))( - optimizerHints, None) + optimizerHints, Unversioned) } } } @@ -2323,37 +2340,19 @@ class JSCodeGen()(using genCtx: Context) { // Partition class members. val privateFieldDefs = mutable.ListBuffer.empty[js.FieldDef] - val classDefMembers = mutable.ListBuffer.empty[js.MemberDef] - val instanceMembers = mutable.ListBuffer.empty[js.MemberDef] - var constructor: Option[js.JSConstructorDef] = None + val jsFieldDefs = mutable.ListBuffer.empty[js.JSFieldDef] - originalClassDef.memberDefs.foreach { + originalClassDef.fields.foreach { case fdef: js.FieldDef => privateFieldDefs += fdef case fdef: js.JSFieldDef => - instanceMembers += fdef - - case mdef: js.MethodDef => - assert(mdef.flags.namespace.isStatic, - "Non-static, unexported method in non-native JS class") - classDefMembers += mdef - - case cdef: js.JSConstructorDef => - assert(constructor.isEmpty, "two ctors in class") - constructor = Some(cdef) - - case mdef: js.JSMethodDef => - assert(!mdef.flags.namespace.isStatic, "Exported static method") - instanceMembers += mdef - - case property: js.JSPropertyDef => - instanceMembers += property - - case nativeMemberDef: js.JSNativeMemberDef => - throw new FatalError("illegal native JS member in JS class at " + nativeMemberDef.pos) + jsFieldDefs += fdef } + assert(originalClassDef.jsNativeMembers.isEmpty, + "Found JS native members in anonymous JS class at " + pos) + assert(originalClassDef.topLevelExportDefs.isEmpty, "Found top-level exports in anonymous JS class at " + pos) @@ -2363,8 +2362,9 @@ class JSCodeGen()(using genCtx: Context) { val parent = js.ClassIdent(jsNames.ObjectClass) js.ClassDef(originalClassDef.name, originalClassDef.originalName, ClassKind.AbstractJSType, None, Some(parent), interfaces = Nil, - jsSuperClass = None, jsNativeLoadSpec = None, - classDefMembers.toList, Nil)( + jsSuperClass = None, jsNativeLoadSpec = None, fields = Nil, + methods = originalClassDef.methods, jsConstructor = None, + jsMethodProps = Nil, jsNativeMembers = Nil, topLevelExportDefs = Nil)( originalClassDef.optimizerHints) } @@ -2375,7 +2375,7 @@ class JSCodeGen()(using genCtx: Context) { val jsClassCaptures = originalClassDef.jsClassCaptures.getOrElse { throw new AssertionError(s"no class captures for anonymous JS class at $pos") } - val js.JSConstructorDef(_, ctorParams, ctorRestParam, ctorBody) = constructor.getOrElse { + val js.JSConstructorDef(_, ctorParams, ctorRestParam, ctorBody) = originalClassDef.jsConstructor.getOrElse { throw new AssertionError("No ctor found") } assert(ctorParams.isEmpty && ctorRestParam.isEmpty, @@ -2399,20 +2399,12 @@ class JSCodeGen()(using genCtx: Context) { def memberLambda(params: List[js.ParamDef], restParam: Option[js.ParamDef], body: js.Tree)(implicit pos: ir.Position): js.Closure = js.Closure(arrow = false, captureParams = Nil, params, restParam, body, captureValues = Nil) - val memberDefinitions0 = instanceMembers.toList.map { - case fdef: js.FieldDef => - throw new AssertionError("unexpected FieldDef") - - case fdef: js.JSFieldDef => - implicit val pos = fdef.pos - js.Assign(js.JSSelect(selfRef, fdef.name), jstpe.zeroOf(fdef.ftpe)) - - case mdef: js.MethodDef => - throw new AssertionError("unexpected MethodDef") - - case cdef: js.JSConstructorDef => - throw new AssertionError("unexpected JSConstructorDef") + val fieldDefinitions = jsFieldDefs.toList.map { fdef => + implicit val pos = fdef.pos + js.Assign(js.JSSelect(selfRef, fdef.name), jstpe.zeroOf(fdef.ftpe)) + } + val memberDefinitions0 = originalClassDef.jsMethodProps.toList.map { case mdef: js.JSMethodDef => implicit val pos = mdef.pos val impl = memberLambda(mdef.args, mdef.restParam, mdef.body) @@ -2434,13 +2426,12 @@ class JSCodeGen()(using genCtx: Context) { js.JSMethodApply(js.JSGlobalRef("Object"), js.StringLiteral("defineProperty"), List(selfRef, pdef.name, descriptor)) - - case nativeMemberDef: js.JSNativeMemberDef => - throw new FatalError("illegal native JS member in JS class at " + nativeMemberDef.pos) } + val memberDefinitions1 = fieldDefinitions ::: memberDefinitions0 + val memberDefinitions = if (privateFieldDefs.isEmpty) { - memberDefinitions0 + memberDefinitions1 } else { /* Private fields, declared in FieldDefs, are stored in a separate * object, itself stored as a non-enumerable field of the `selfRef`. @@ -2481,7 +2472,7 @@ class JSCodeGen()(using genCtx: Context) { ) ) } - definePrivateFieldsObj :: memberDefinitions0 + definePrivateFieldsObj :: memberDefinitions1 } // Transform the constructor body. @@ -3581,7 +3572,7 @@ class JSCodeGen()(using genCtx: Context) { NoOriginalName, paramDefs, jstpe.AnyType, - Some(body))(OptimizerHints.empty, None) + Some(body))(OptimizerHints.empty, Unversioned) } } diff --git a/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala b/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala index 8c72f03e7cc4..b5f9446758a9 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala @@ -27,6 +27,7 @@ import org.scalajs.ir.Names.DefaultModuleID import org.scalajs.ir.OriginalName.NoOriginalName import org.scalajs.ir.Position.NoPosition import org.scalajs.ir.Trees.OptimizerHints +import org.scalajs.ir.Version.Unversioned import dotty.tools.dotc.transform.sjs.JSExportUtils.* import dotty.tools.dotc.transform.sjs.JSSymUtils.* @@ -185,7 +186,7 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) { }).toList } - def genStaticExports(classSym: Symbol): List[js.MemberDef] = { + def genStaticExports(classSym: Symbol): (List[js.JSFieldDef], List[js.JSMethodPropDef]) = { val exports = for { sym <- classSym.info.decls.toList info <- staticExportsOf(sym) @@ -193,10 +194,13 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) { (info, sym) } - (for { + val fields = List.newBuilder[js.JSFieldDef] + val methodProps = List.newBuilder[js.JSMethodPropDef] + + for { (info, tups) <- exports.groupBy(_._1) kind <- checkSameKind(tups) - } yield { + } { def alts = tups.map(_._2) implicit val pos = info.pos @@ -205,10 +209,12 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) { kind match { case Method => - genMemberExportOrDispatcher(JSName.Literal(info.jsName), isProp = false, alts, static = true) + methodProps += + genMemberExportOrDispatcher(JSName.Literal(info.jsName), isProp = false, alts, static = true) case Property => - genMemberExportOrDispatcher(JSName.Literal(info.jsName), isProp = true, alts, static = true) + methodProps += + genMemberExportOrDispatcher(JSName.Literal(info.jsName), isProp = true, alts, static = true) case Field => val sym = checkSingleField(tups) @@ -219,19 +225,21 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) { .withMutable(true) val name = js.StringLiteral(info.jsName) val irTpe = genExposedFieldIRType(sym) - js.JSFieldDef(flags, name, irTpe) + fields += js.JSFieldDef(flags, name, irTpe) case kind => throw new AssertionError(s"unexpected static export kind: $kind") } - }).toList + } + + (fields.result(), methodProps.result()) } /** Generates exported methods and properties for a class. * * @param classSym symbol of the class we export for */ - def genMemberExports(classSym: ClassSymbol): List[js.MemberDef] = { + def genMemberExports(classSym: ClassSymbol): List[js.JSMethodPropDef] = { val classInfo = classSym.info val allExports = classInfo.memberDenots(takeAllFilter, { (name, buf) => if (isExportName(name)) @@ -251,7 +259,7 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) { newlyDeclaredExportNames.map(genMemberExport(classSym, _)) } - private def genMemberExport(classSym: ClassSymbol, name: TermName): js.MemberDef = { + private def genMemberExport(classSym: ClassSymbol, name: TermName): js.JSMethodPropDef = { /* This used to be `.member(name)`, but it caused #3538, since we were * sometimes selecting mixin forwarders, whose type history does not go * far enough back in time to see varargs. We now explicitly exclude @@ -284,11 +292,11 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) { genMemberExportOrDispatcher(JSName.Literal(jsName), isProp, alts.map(_.symbol), static = false) } - def genJSClassDispatchers(classSym: Symbol, dispatchMethodsNames: List[JSName]): List[js.MemberDef] = { + def genJSClassDispatchers(classSym: Symbol, dispatchMethodsNames: List[JSName]): List[js.JSMethodPropDef] = { dispatchMethodsNames.map(genJSClassDispatcher(classSym, _)) } - private def genJSClassDispatcher(classSym: Symbol, name: JSName): js.MemberDef = { + private def genJSClassDispatcher(classSym: Symbol, name: JSName): js.JSMethodPropDef = { val alts = classSym.info.membersBasedOnFlags(required = Method, excluded = Bridge) .map(_.symbol) .filter { sym => @@ -311,14 +319,14 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) { em"Conflicting properties and methods for ${classSym.fullName}::$name.", firstAlt.srcPos) implicit val pos = firstAlt.span - js.JSPropertyDef(js.MemberFlags.empty, genExpr(name)(firstAlt.sourcePos), None, None) + js.JSPropertyDef(js.MemberFlags.empty, genExpr(name)(firstAlt.sourcePos), None, None)(Unversioned) } else { genMemberExportOrDispatcher(name, isProp, alts, static = false) } } private def genMemberExportOrDispatcher(jsName: JSName, isProp: Boolean, - alts: List[Symbol], static: Boolean): js.MemberDef = { + alts: List[Symbol], static: Boolean): js.JSMethodPropDef = { withNewLocalNameScope { if (isProp) genExportProperty(alts, jsName, static) @@ -362,7 +370,7 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) { } } - js.JSPropertyDef(flags, genExpr(jsName)(alts.head.sourcePos), getterBody, setterArgAndBody) + js.JSPropertyDef(flags, genExpr(jsName)(alts.head.sourcePos), getterBody, setterArgAndBody)(Unversioned) } private def genExportMethod(alts0: List[Symbol], jsName: JSName, static: Boolean)(using Context): js.JSMethodDef = { @@ -389,7 +397,7 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) { genOverloadDispatch(jsName, overloads, jstpe.AnyType) js.JSMethodDef(flags, genExpr(jsName), formalArgs, restParam, body)( - OptimizerHints.empty, None) + OptimizerHints.empty, Unversioned) } def genOverloadDispatch(jsName: JSName, alts: List[Exported], tpe: jstpe.Type)( diff --git a/compiler/src/dotty/tools/dotc/transform/Mixin.scala b/compiler/src/dotty/tools/dotc/transform/Mixin.scala index 6df4bebde132..9a19c0dc414f 100644 --- a/compiler/src/dotty/tools/dotc/transform/Mixin.scala +++ b/compiler/src/dotty/tools/dotc/transform/Mixin.scala @@ -18,6 +18,8 @@ import NameKinds.* import NameOps.* import ast.Trees.* +import dotty.tools.dotc.transform.sjs.JSSymUtils.isJSType + object Mixin { val name: String = "mixin" val description: String = "expand trait fields and trait initializers" @@ -273,7 +275,15 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase => else if (getter.is(Lazy, butNot = Module)) transformFollowing(superRef(getter).appliedToNone) else if (getter.is(Module)) - New(getter.info.resultType, List(This(cls))) + if ctx.settings.scalajs.value && getter.moduleClass.isJSType then + if getter.is(Scala2x) then + report.error( + em"""Implementation restriction: cannot extend the Scala 2 trait $mixin + |containing the object $getter that extends js.Any""", + cls.srcPos) + transformFollowing(superRef(getter).appliedToNone) + else + New(getter.info.resultType, List(This(cls))) else Underscore(getter.info.resultType) // transformFollowing call is needed to make memoize & lazy vals run diff --git a/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSExports.scala b/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSExports.scala index dbd6e1a8f412..f66141bff8ad 100644 --- a/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSExports.scala +++ b/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSExports.scala @@ -45,101 +45,28 @@ object PrepJSExports { private final case class ExportInfo(jsName: String, destination: ExportDestination)(val pos: SrcPos) - /** Checks a class or module class for export. + /** Generate exports for the given Symbol. * - * Note that non-module Scala classes are never actually exported; their constructors are. - * However, the checks are performed on the class when the class is annotated. + * - Registers top-level and static exports. + * - Returns (non-static) exporters for this symbol. */ - def checkClassOrModuleExports(sym: Symbol)(using Context): Unit = { - val exports = exportsOf(sym) - if (exports.nonEmpty) - checkClassOrModuleExports(sym, exports.head.pos) - } + def genExport(sym: Symbol)(using Context): List[Tree] = { + // Scala classes are never exported: Their constructors are. + val isScalaClass = sym.isClass && !sym.isOneOf(Trait | Module) && !isJSAny(sym) - /** Generate the exporter for the given DefDef or ValDef. - * - * If this DefDef is a constructor, it is registered to be exported by - * GenJSCode instead and no trees are returned. - */ - def genExportMember(baseSym: Symbol)(using Context): List[Tree] = { - val clsSym = baseSym.owner + // Filter constructors of module classes: The module classes themselves will be exported. + val isModuleClassCtor = sym.isConstructor && sym.owner.is(ModuleClass) - val exports = exportsOf(baseSym) + val exports = + if (isScalaClass || isModuleClassCtor) Nil + else exportsOf(sym) - // Helper function for errors - def err(msg: String): List[Tree] = { - report.error(msg, exports.head.pos) - Nil - } + assert(exports.isEmpty || !sym.is(Bridge), + s"found exports for bridge symbol $sym. exports: $exports") - def memType = if (baseSym.isConstructor) "constructor" else "method" - - if (exports.isEmpty) { - Nil - } else if (!hasLegalExportVisibility(baseSym)) { - err(s"You may only export public and protected ${memType}s") - } else if (baseSym.is(Inline)) { - err("You may not export an inline method") - } else if (isJSAny(clsSym)) { - err(s"You may not export a $memType of a subclass of js.Any") - } else if (baseSym.isLocalToBlock) { - err("You may not export a local definition") - } else if (hasIllegalRepeatedParam(baseSym)) { - err(s"In an exported $memType, a *-parameter must come last (through all parameter lists)") - } else if (hasIllegalDefaultParam(baseSym)) { - err(s"In an exported $memType, all parameters with defaults must be at the end") - } else if (baseSym.isConstructor) { - // Constructors do not need an exporter method. We only perform the checks at this phase. - checkClassOrModuleExports(clsSym, exports.head.pos) - Nil - } else { - assert(!baseSym.is(Bridge), s"genExportMember called for bridge symbol $baseSym") - val normalExports = exports.filter(_.destination == ExportDestination.Normal) - normalExports.flatMap(exp => genExportDefs(baseSym, exp.jsName, exp.pos.span)) - } - } - - /** Check a class or module for export. - * - * There are 2 ways that this method can be reached: - * - via `registerClassExports` - * - via `genExportMember` (constructor of Scala class) - */ - private def checkClassOrModuleExports(sym: Symbol, errPos: SrcPos)(using Context): Unit = { - val isMod = sym.is(ModuleClass) - - def err(msg: String): Unit = - report.error(msg, errPos) - - def hasAnyNonPrivateCtor: Boolean = - sym.info.decl(nme.CONSTRUCTOR).hasAltWith(denot => !isPrivateMaybeWithin(denot.symbol)) - - if (sym.is(Trait)) { - err("You may not export a trait") - } else if (sym.hasAnnotation(jsdefn.JSNativeAnnot)) { - err("You may not export a native JS " + (if (isMod) "object" else "class")) - } else if (!hasLegalExportVisibility(sym)) { - err("You may only export public and protected " + (if (isMod) "objects" else "classes")) - } else if (isJSAny(sym.owner)) { - err("You may not export a " + (if (isMod) "object" else "class") + " in a subclass of js.Any") - } else if (sym.isLocalToBlock) { - err("You may not export a local " + (if (isMod) "object" else "class")) - } else if (!sym.isStatic) { - if (isMod) - err("You may not export a nested object") - else - err("You may not export a nested class. Create an exported factory method in the outer class to work around this limitation.") - } else if (sym.is(Abstract, butNot = Trait) && !isJSAny(sym)) { - err("You may not export an abstract class") - } else if (!isMod && !hasAnyNonPrivateCtor) { - /* This test is only relevant for JS classes but doesn't hurt for Scala - * classes as we could not reach it if there were only private - * constructors. - */ - err("You may not export a class that has only private constructors") - } else { - // OK - } + // For normal exports, generate exporter methods. + val normalExports = exports.filter(_.destination == ExportDestination.Normal) + normalExports.flatMap(exp => genExportDefs(sym, exp.jsName, exp.pos.span)) } /** Computes the ExportInfos for sym from its annotations. */ @@ -152,6 +79,10 @@ object PrepJSExports { else sym } + val symOwner = + if (sym.isConstructor) sym.owner.owner + else sym.owner + val JSExportAnnot = jsdefn.JSExportAnnot val JSExportTopLevelAnnot = jsdefn.JSExportTopLevelAnnot val JSExportStaticAnnot = jsdefn.JSExportStaticAnnot @@ -161,19 +92,42 @@ object PrepJSExports { val directMemberAnnots = Set[Symbol](JSExportAnnot, JSExportTopLevelAnnot, JSExportStaticAnnot) val directAnnots = trgSym.annotations.filter(annot => directMemberAnnots.contains(annot.symbol)) - // Is this a member export (i.e. not a class or module export)? - val isMember = !sym.isClass && !sym.isConstructor - - // Annotations for this member on the whole unit + /* Annotations for this member on the whole unit + * + * Note that for top-level classes / modules this is always empty, because + * packages cannot have annotations. + */ val unitAnnots = { - if (isMember && sym.isPublic && !sym.is(Synthetic)) - sym.owner.annotations.filter(_.symbol == JSExportAllAnnot) + val useExportAll = { + sym.isPublic && + !sym.is(Synthetic) && + !sym.isConstructor && + !sym.is(Trait) && + (!sym.isClass || sym.is(ModuleClass)) + } + + if (useExportAll) + symOwner.annotations.filter(_.symbol == JSExportAllAnnot) else Nil } + val allAnnots = { + val allAnnots0 = directAnnots ++ unitAnnots + + if (allAnnots0.nonEmpty) { + val errorPos: SrcPos = + if (allAnnots0.head.symbol == JSExportAllAnnot) sym + else allAnnots0.head.tree + if (checkExportTarget(sym, errorPos)) allAnnots0 + else Nil // prevent code generation from running to avoid crashes. + } else { + Nil + } + } + val allExportInfos = for { - annot <- directAnnots ++ unitAnnots + annot <- allAnnots } yield { val isExportAll = annot.symbol == JSExportAllAnnot val isTopLevelExport = annot.symbol == JSExportTopLevelAnnot @@ -194,7 +148,13 @@ object PrepJSExports { "dummy" } } else { - sym.defaultJSName + val name = (if (sym.isConstructor) sym.owner else sym).defaultJSName + if (name.endsWith(str.SETTER_SUFFIX) && !sym.isJSSetter) { + report.error( + "You must set an explicit name when exporting a non-setter with a name ending in _=", + exportPos) + } + name } } @@ -217,20 +177,33 @@ object PrepJSExports { } } - // Enforce proper setter signature - if (sym.isJSSetter) - checkSetterSignature(sym, exportPos, exported = true) - // Enforce no __ in name if (!isTopLevelExport && name.contains("__")) report.error("An exported name may not contain a double underscore (`__`)", exportPos) - /* Illegal function application exports, i.e., method named 'apply' - * without an explicit export name. - */ - if (isMember && !hasExplicitName && sym.name == nme.apply) { - destination match { - case ExportDestination.Normal => + // Destination-specific restrictions + destination match { + case ExportDestination.Normal => + // Disallow @JSExport on top-level definitions. + if (symOwner.is(Package) || symOwner.isPackageObject) { + report.error("@JSExport is forbidden on top-level definitions. Use @JSExportTopLevel instead.", exportPos) + } + + // Make sure we do not override the default export of toString + def isIllegalToString = { + name == "toString" && sym.name != nme.toString_ && + sym.info.paramInfoss.forall(_.isEmpty) && !sym.isJSGetter + } + if (isIllegalToString) { + report.error( + "You may not export a zero-argument method named other than 'toString' under the name 'toString'", + exportPos) + } + + /* Illegal function application exports, i.e., method named 'apply' + * without an explicit export name. + */ + if (!hasExplicitName && sym.name == nme.apply) { def shouldBeTolerated = { isExportAll && directAnnots.exists { annot => annot.symbol == JSExportAnnot && @@ -246,44 +219,6 @@ object PrepJSExports { "Add @JSExport(\"apply\") to export under the name apply.", exportPos) } - - case _: ExportDestination.TopLevel => - throw new AssertionError( - em"Found a top-level export without an explicit name at ${exportPos.sourcePos}") - - case ExportDestination.Static => - report.error( - "A member cannot be exported to function application as static. " + - "Use @JSExportStatic(\"apply\") to export it under the name 'apply'.", - exportPos) - } - } - - val symOwner = - if (sym.isConstructor) sym.owner.owner - else sym.owner - - // Destination-specific restrictions - destination match { - case ExportDestination.Normal => - // Make sure we do not override the default export of toString - def isIllegalToString = { - isMember && name == "toString" && sym.name != nme.toString_ && - sym.info.paramInfoss.forall(_.isEmpty) && !sym.isJSGetter - } - if (isIllegalToString) { - report.error( - "You may not export a zero-argument method named other than 'toString' under the name 'toString'", - exportPos) - } - - // Disallow @JSExport at the top-level, as well as on objects and classes - if (symOwner.is(Package) || symOwner.isPackageObject) { - report.error("@JSExport is forbidden on top-level definitions. Use @JSExportTopLevel instead.", exportPos) - } else if (!isMember && !sym.is(Trait)) { - report.error( - "@JSExport is forbidden on objects and classes. Use @JSExport'ed factory methods instead.", - exportPos) } case _: ExportDestination.TopLevel => @@ -292,10 +227,8 @@ object PrepJSExports { else if (sym.is(Method, butNot = Accessor) && sym.isJSProperty) report.error("You may not export a getter or a setter to the top level", exportPos) - /* Disallow non-static methods. - * Note: Non-static classes have more specific error messages in checkClassOrModuleExports. - */ - if (sym.isTerm && (!symOwner.isStatic || !symOwner.is(ModuleClass))) + // Disallow non-static definitions. + if (!symOwner.isStatic || !symOwner.is(ModuleClass)) report.error("Only static objects may export their members to the top level", exportPos) // The top-level name must be a valid JS identifier @@ -317,14 +250,20 @@ object PrepJSExports { exportPos) } - if (isMember) { - if (sym.is(Lazy)) - report.error("You may not export a lazy val as static", exportPos) - } else { - if (sym.is(Trait)) - report.error("You may not export a trait as static.", exportPos) - else - report.error("Implementation restriction: cannot export a class or object as static", exportPos) + if (sym.is(Lazy)) + report.error("You may not export a lazy val as static", exportPos) + + // Illegal function application export + if (!hasExplicitName && sym.name == nme.apply) { + report.error( + "A member cannot be exported to function application as " + + "static. Use @JSExportStatic(\"apply\") to export it under " + + "the name 'apply'.", + exportPos) + } + + if (sym.isClass || sym.isConstructor) { + report.error("Implementation restriction: cannot export a class or object as static", exportPos) } } @@ -342,9 +281,9 @@ object PrepJSExports { } .foreach(_ => report.warning("Found duplicate @JSExport", sym)) - /* Make sure that no field is exported *twice* as static, nor both as - * static and as top-level (it is possible to export a field several times - * as top-level, though). + /* Check that no field is exported *twice* as static, nor both as static + * and as top-level (it is possible to export a field several times as + * top-level, though). */ if (!sym.is(Method)) { for (firstStatic <- allExportInfos.find(_.destination == ExportDestination.Static)) { @@ -370,32 +309,114 @@ object PrepJSExports { allExportInfos.distinct } + /** Checks whether the given target is suitable for export and exporting + * should be performed. + * + * Reports any errors for unsuitable targets. + * @returns a boolean indicating whether exporting should be performed. Note: + * a result of true is not a guarantee that no error was emitted. But it is + * a guarantee that the target is not "too broken" to run the rest of + * the generation. This approximation is done to avoid having to complicate + * shared code verifying conditions. + */ + private def checkExportTarget(sym: Symbol, errPos: SrcPos)(using Context): Boolean = { + def err(msg: String): Boolean = { + report.error(msg, errPos) + false + } + + def hasLegalExportVisibility(sym: Symbol): Boolean = + sym.isPublic || sym.is(Protected, butNot = Local) + + def isMemberOfJSAny: Boolean = + isJSAny(sym.owner) || (sym.isConstructor && isJSAny(sym.owner.owner)) + + def hasIllegalRepeatedParam: Boolean = { + val paramInfos = sym.info.paramInfoss.flatten + paramInfos.nonEmpty && paramInfos.init.exists(_.isRepeatedParam) + } + + def hasIllegalDefaultParam: Boolean = { + sym.hasDefaultParams + && sym.paramSymss.flatten.reverse.dropWhile(_.is(HasDefault)).exists(_.is(HasDefault)) + } + + def hasAnyNonPrivateCtor: Boolean = + sym.info.member(nme.CONSTRUCTOR).hasAltWith(d => !isPrivateMaybeWithin(d.symbol)) + + if (sym.is(Trait)) { + err("You may not export a trait") + } else if (sym.hasAnnotation(jsdefn.JSNativeAnnot)) { + err("You may not export a native JS definition") + } else if (!hasLegalExportVisibility(sym)) { + err("You may only export public and protected definitions") + } else if (sym.isConstructor && !hasLegalExportVisibility(sym.owner)) { + err("You may only export constructors of public and protected classes") + } else if (sym.is(Macro)) { + err("You may not export a macro") + } else if (isMemberOfJSAny) { + err("You may not export a member of a subclass of js.Any") + } else if (sym.isLocalToBlock) { + err("You may not export a local definition") + } else if (sym.isConstructor && sym.owner.isLocalToBlock) { + err("You may not export constructors of local classes") + } else if (hasIllegalRepeatedParam) { + err("In an exported method or constructor, a *-parameter must come last " + + "(through all parameter lists)") + } else if (hasIllegalDefaultParam) { + err("In an exported method or constructor, all parameters with " + + "defaults must be at the end") + } else if (sym.isConstructor && sym.owner.is(Abstract, butNot = Trait) && !isJSAny(sym)) { + err("You may not export an abstract class") + } else if (sym.isClass && !sym.is(ModuleClass) && isJSAny(sym) && !hasAnyNonPrivateCtor) { + /* This test is only relevant for JS classes: We'll complain on the + * individual exported constructors in case of a Scala class. + */ + err("You may not export a class that has only private constructors") + } else { + if (sym.isJSSetter) + checkSetterSignature(sym, errPos, exported = true) + + true // ok even if a setter has the wrong signature. + } + } + /** Generates an exporter for a DefDef including default parameter methods. */ - private def genExportDefs(defSym: Symbol, jsName: String, span: Span)(using Context): List[Tree] = { - val clsSym = defSym.owner.asClass + private def genExportDefs(sym: Symbol, jsName: String, span: Span)(using Context): List[Tree] = { + val siblingSym = + if (sym.isConstructor) sym.owner + else sym + + val clsSym = siblingSym.owner.asClass + + val isProperty = sym.is(ModuleClass) || isJSAny(sym) || sym.isJSProperty + + val copiedFlags0 = (siblingSym.flags & (Protected | Final)).toTermFlags + val copiedFlags = + if (siblingSym.is(HasDefaultParams)) copiedFlags0 | HasDefaultParams // term flag only + else copiedFlags0 // Create symbol for new method - val name = makeExportName(jsName, !defSym.is(Method) || defSym.isJSProperty) - val flags = (defSym.flags | Method | Synthetic) - &~ (Deferred | Accessor | ParamAccessor | CaseAccessor | Mutable | Lazy | Override) + val scalaName = makeExportName(jsName, !sym.is(Method) || sym.isJSProperty) + val flags = Method | Synthetic | copiedFlags val info = - if (defSym.isConstructor) defSym.info - else if (defSym.is(Method)) finalResultTypeToAny(defSym.info) + if (sym.isConstructor) sym.info + else if (sym.is(Method)) finalResultTypeToAny(sym.info) else ExprType(defn.AnyType) - val expSym = newSymbol(clsSym, name, flags, info, defSym.privateWithin, span).entered + val expSym = newSymbol(clsSym, scalaName, flags, info, sym.privateWithin, span).entered // Construct exporter DefDef tree - val exporter = genProxyDefDef(clsSym, defSym, expSym, span) + val exporter = genProxyDefDef(clsSym, sym, expSym, span) // Construct exporters for default getters - val defaultGetters = if (!defSym.hasDefaultParams) { + val defaultGetters = if (!sym.hasDefaultParams) { Nil } else { for { - (param, i) <- defSym.paramSymss.flatten.zipWithIndex + (param, i) <- sym.paramSymss.flatten.zipWithIndex if param.is(HasDefault) } yield { - genExportDefaultGetter(clsSym, defSym, expSym, i, span) + genExportDefaultGetter(clsSym, sym, expSym, i, span) } } @@ -431,7 +452,27 @@ object PrepJSExports { proxySym: TermSymbol, span: Span)(using Context): Tree = { DefDef(proxySym, { argss => - This(clsSym).select(trgSym).appliedToArgss(argss) + if (trgSym.isConstructor) { + val tycon = trgSym.owner.typeRef + New(tycon).select(TermRef(tycon, trgSym)).appliedToArgss(argss) + } else if (trgSym.is(ModuleClass)) { + assert(argss.isEmpty, + s"got a module export with non-empty paramss. target: $trgSym, proxy: $proxySym at $span") + ref(trgSym.sourceModule) + } else if (trgSym.isClass) { + assert(isJSAny(trgSym), s"got a class export for a non-JS class ($trgSym) at $span") + val tpe = argss match { + case Nil => + trgSym.typeRef + case (targs @ (first :: _)) :: Nil if first.isType => + trgSym.typeRef.appliedTo(targs.map(_.tpe)) + case _ => + throw AssertionError(s"got a class export with unexpected paramss. target: $trgSym, proxy: $proxySym at $span") + } + ref(jsdefn.JSPackage_constructorOf).appliedToType(tpe) + } else { + This(clsSym).select(trgSym).appliedToArgss(argss) + } }).withSpan(span) } @@ -448,20 +489,4 @@ object PrepJSExports { case _ => defn.AnyType } - - /** Whether the given symbol has a visibility that allows exporting */ - private def hasLegalExportVisibility(sym: Symbol)(using Context): Boolean = - sym.isPublic || sym.is(Protected, butNot = Local) - - /** Checks whether this type has a repeated parameter elsewhere than at the end of all the params. */ - private def hasIllegalRepeatedParam(sym: Symbol)(using Context): Boolean = { - val paramInfos = sym.info.paramInfoss.flatten - paramInfos.nonEmpty && paramInfos.init.exists(_.isRepeatedParam) - } - - /** Checks whether there are default parameters not at the end of the flattened parameter list. */ - private def hasIllegalDefaultParam(sym: Symbol)(using Context): Boolean = { - sym.hasDefaultParams - && sym.paramSymss.flatten.reverse.dropWhile(_.is(HasDefault)).exists(_.is(HasDefault)) - } } diff --git a/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSInterop.scala b/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSInterop.scala index 610fca869ad2..1b8fdd268ece 100644 --- a/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSInterop.scala +++ b/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSInterop.scala @@ -160,7 +160,9 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP tree match { case tree: TypeDef if tree.isClassDef => - checkClassOrModuleExports(sym) + val exports = genExport(sym) + if (exports.nonEmpty) + exporters.getOrElseUpdate(sym.owner, mutable.ListBuffer.empty) ++= exports if (isJSAny(sym)) transformJSClassDef(tree) @@ -172,7 +174,11 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP case tree: ValOrDefDef => // Prepare exports - exporters.getOrElseUpdate(sym.owner, mutable.ListBuffer.empty) ++= genExportMember(sym) + val exports = genExport(sym) + if (exports.nonEmpty) { + val target = if (sym.isConstructor) sym.owner.owner else sym.owner + exporters.getOrElseUpdate(target, mutable.ListBuffer.empty) ++= exports + } if (sym.isLocalToBlock) super.transform(tree) @@ -247,6 +253,8 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP exporters.get(clsSym).fold { transformedTree } { exports => + assert(exports.nonEmpty, s"found empty exporters for $clsSym" ) + checkNoDoubleDeclaration(clsSym) cpy.Template(transformedTree)( diff --git a/project/Build.scala b/project/Build.scala index 8142e5e0744d..ed2460820d07 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1513,6 +1513,7 @@ object Build { "compliantArrayIndexOutOfBounds" -> (sems.arrayIndexOutOfBounds == CheckedBehavior.Compliant), "compliantArrayStores" -> (sems.arrayStores == CheckedBehavior.Compliant), "compliantNegativeArraySizes" -> (sems.negativeArraySizes == CheckedBehavior.Compliant), + "compliantNullPointers" -> (sems.nullPointers == CheckedBehavior.Compliant), "compliantStringIndexOutOfBounds" -> (sems.stringIndexOutOfBounds == CheckedBehavior.Compliant), "compliantModuleInit" -> (sems.moduleInit == CheckedBehavior.Compliant), "strictFloats" -> sems.strictFloats, @@ -1579,6 +1580,7 @@ object Build { (dir / "shared/src/test/scala" ** (("*.scala": FileFilter) -- "ReflectiveCallTest.scala" // uses many forms of structural calls that are not allowed in Scala 3 anymore -- "UTF16Test.scala" // refutable pattern match + -- "CharsetTest.scala" // bogus @tailrec that Scala 2 ignores but Scala 3 flags as an error )).get ++ (dir / "shared/src/test/require-sam" ** "*.scala").get diff --git a/project/plugins.sbt b/project/plugins.sbt index c94d4d5afe8d..d378848561b8 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -6,7 +6,7 @@ libraryDependencySchemes += "org.scala-lang.modules" %% "scala-xml" % VersionScheme.Always -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.12.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.13.0") addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.21") diff --git a/tests/neg-scalajs/jsexport-on-non-toplevel-class-object.scala b/tests/neg-scalajs/jsexport-on-non-toplevel-class-object.scala deleted file mode 100644 index 7d127a5654ae..000000000000 --- a/tests/neg-scalajs/jsexport-on-non-toplevel-class-object.scala +++ /dev/null @@ -1,30 +0,0 @@ -import scala.scalajs.js -import scala.scalajs.js.annotation.* - -class A { - @JSExport // error - class A1 { - @JSExport // error - def this(x: Int) = this() - } - - @JSExport // error - class A2 extends js.Object - - @JSExport // error - object A3 - - @JSExport // error - object A4 extends js.Object -} - -object B { - @JSExport // error - class B1 { - @JSExport // error - def this(x: Int) = this() - } - - @JSExport // error - class B2 extends js.Object -}