Skip to content

Scala.js: Support for reflective calls. #9427

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -512,9 +512,16 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {

case ClazzTag =>
val tp = toTypeKind(const.typeValue)
// classOf[Int] is transformed to Integer.TYPE by ClassOf
assert(!tp.isPrimitive, s"expected class type in classOf[T], found primitive type $tp")
mnode.visitLdcInsn(tp.toASMType)
if tp.isPrimitive then
val boxedClass = boxedClassOfPrimitive(tp.asPrimitiveBType)
mnode.visitFieldInsn(
asm.Opcodes.GETSTATIC,
boxedClass.internalName,
"TYPE", // field name
jlClassRef.descriptor
)
else
mnode.visitLdcInsn(tp.toASMType)

case EnumTag =>
val sym = const.symbolValue
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/backend/jvm/CoreBTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: DottyBackendInterface]](val bTyp
lazy val jlStringBuilderRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.StringBuilder])
lazy val jlStringBufferRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.StringBuffer])
lazy val jlCharSequenceRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.CharSequence])
lazy val jlClassRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Class[_]])
lazy val ThrowableReference : ClassBType = classBTypeFromSymbol(defn.ThrowableClass)
lazy val jlCloneableReference : ClassBType = classBTypeFromSymbol(defn.JavaCloneableClass) // java/lang/Cloneable
lazy val jlNPEReference : ClassBType = classBTypeFromSymbol(defn.NullPointerExceptionClass) // java/lang/NullPointerException
Expand Down Expand Up @@ -248,6 +249,7 @@ final class CoreBTypesProxy[BTFS <: BTypesFromSymbols[_ <: DottyBackendInterface
def jlStringBuilderRef : ClassBType = _coreBTypes.jlStringBuilderRef
def jlStringBufferRef : ClassBType = _coreBTypes.jlStringBufferRef
def jlCharSequenceRef : ClassBType = _coreBTypes.jlCharSequenceRef
def jlClassRef : ClassBType = _coreBTypes.jlClassRef
def ThrowableReference : ClassBType = _coreBTypes.ThrowableReference
def jlCloneableReference : ClassBType = _coreBTypes.jlCloneableReference
def jlNPEReference : ClassBType = _coreBTypes.jlNPEReference
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/backend/jvm/scalaPrimitives.scala
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,9 @@ class DottyPrimitives(ictx: Context) {
primitives.toMap
}

def isPrimitive(sym: Symbol): Boolean =
primitives.contains(sym)

def isPrimitive(fun: Tree): Boolean =
given Context = ictx
primitives.contains(fun.symbol)
Expand All @@ -407,4 +410,3 @@ class DottyPrimitives(ictx: Context) {
case _ => true
})
}

181 changes: 177 additions & 4 deletions compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -791,9 +791,9 @@ class JSCodeGen()(using genCtx: Context) {
toIRType(param.info), mutable = false, rest = false)
}

/*if (primitives.isPrimitive(sym)) {
if (primitives.isPrimitive(sym)) {
None
} else*/ if (sym.is(Deferred)) {
} else if (sym.is(Deferred)) {
Some(js.MethodDef(js.MemberFlags.empty, methodName, originalName,
jsParams, toIRType(patchedResultType(sym)), None)(
OptimizerHints.empty, None))
Expand Down Expand Up @@ -2815,9 +2815,181 @@ class JSCodeGen()(using genCtx: Context) {
js.ForIn(objVarDef.ref, keyVarIdent, NoOriginalName, {
js.JSFunctionApply(fVarDef.ref, List(keyVarRef))
}))

case REFLECT_SELECTABLE_SELECTDYN =>
// scala.reflect.Selectable.selectDynamic
genReflectiveCall(tree, isSelectDynamic = true)
case REFLECT_SELECTABLE_APPLYDYN =>
// scala.reflect.Selectable.applyDynamic
genReflectiveCall(tree, isSelectDynamic = false)
}
}

/** Gen the SJSIR for a reflective call.
*
* Reflective calls are calls to a structural type field or method that
* involve a reflective Selectable. They look like the following in source
* code:
* {{{
* import scala.reflect.Selectable.reflectiveSelectable
*
* type Structural = {
* val foo: Int
* def bar(x: Int, y: String): String
* }
*
* val structural: Structural = new {
* val foo: Int = 5
* def bar(x: Int, y: String): String = x.toString + y
* }
*
* structural.foo
* structural.bar(6, "hello")
* }}}
*
* After expansion by the Scala 3 rules for structural member selections and
* calls, they look like
*
* {{{
* reflectiveSelectable(structural).selectDynamic("foo")
* reflectiveSelectable(structural).applyDynamic("bar",
* ClassTag(classOf[Int]), ClassTag(classOf[String])
* )(
* 6, "hello"
* )
* }}}
*
* and eventually reach the back-end as
*
* {{{
* reflectiveSelectable(structural).selectDynamic("foo") // same as above
* reflectiveSelectable(structural).applyDynamic("bar",
* wrapRefArray([ ClassTag(classOf[Int]), ClassTag(classOf[String]) : ClassTag ]
* )(
* genericWrapArray([ Int.box(6), "hello" : Object ])
* )
* }}}
*
* If we use the deprecated `import scala.language.reflectiveCalls`, the
* wrapper for the receiver `structural` are the following instead:
*
* {{{
* reflectiveSelectableFromLangReflectiveCalls(structural)(
* using scala.languageFeature.reflectiveCalls)
* }}}
*
* (in which case we don't care about the contextual argument).
*
* In SJSIR, they must be encoded as follows:
*
* {{{
* structural.foo;R()
* structural.bar;I;Ljava.lang.String;R(
* Int.box(6).asInstanceOf[int],
* "hello".asInstanceOf[java.lang.String]
* )
* }}}
*
* This means that we must deconstruct the elaborated calls to recover:
*
* - the original receiver `structural`
* - the method name as a compile-time string `foo` or `bar`
* - the `tp: Type`s that have been wrapped in `ClassTag(classOf[tp])`, as a
* compile-time List[Type], from which we'll derive `jstpe.Type`s for the
* `asInstanceOf`s and `jstpe.TypeRef`s for the `MethodName.reflectiveProxy`
* - the actual arguments as a compile-time `List[Tree]`
*
* Virtually all of the code in `genReflectiveCall` deals with recovering
* those elements. Constructing the IR Tree is the easy part after that.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a thing like scala.reflect.Selectable seems like a neat trick to easily implement reflective calls on backends that support reflection.

However, here it seems that this trick is now doing the entire compiler a disservice: We have ~100 LOC simply reconstructing things and then a single LOC actually issuing the call. (and additionally failures that IIUC simply cannot happen with reflective calls).

Have you considered introducing a form of transient tree and doing the translation to a call to Selectable later? (I realize that the call relies on other transformations that happen earlier, so it might be worse overall).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that the situation is very inconvenient. But the desugaring that is now done in Typer is actually specced as is now. It's not "a trick". It is the normal way of compiling calls to members of structural types. reflectiveSelectable is just one possible implicit that can provide an implementation for those, but there can be other, user-defined implementation (that don't really on Java reflection, but on an internal Map, for example).

The reference is here: https://dotty.epfl.ch/docs/reference/changed-features/structural-types.html

We need to support reflectiveSelectable because it corresponds to Scala 2 reflective calls, and it's the only way that our back-end would emit calls to reflective proxies (which we really need in some cases; we use them in the JDK implem in Scala.js itself for example).

Users can implement other Selectables not relying on Java run-time reflection, and those would also work.

So we can't introduce a form of transient tree to be desugared later. The desugaring is by spec and requires interactions with other tasks of typechecking, including implicit resolution. It's impossible to delay that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm... I see. So is the expectation that any custom Selectable that needs compile time reflection needs to do this ceremony? The page you linked doesn't go into details about that.

Further, if that is the case, shouldn't we introduce another Selectable for Scala.js? The main issue I see here is that with the current specification, it would be allowed to use reflectiveSelectable on its own, without structural types. However, such use would immediately lead to non-portable code. Maybe we should make this more obvious?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize this is probably a larger discussion, so you might want to go ahead with this PR. I still think it is worth thinking about this a bit more.

*/
private def genReflectiveCall(tree: Apply, isSelectDynamic: Boolean): js.Tree = {
implicit val pos = tree.span
val Apply(fun @ Select(receiver0, _), args) = tree

/* Extract the real receiver, which is the first argument to one of the
* implicit conversions scala.reflect.Selectable.reflectiveSelectable or
* scala.Selectable.reflectiveSelectableFromLangReflectiveCalls.
*/
val receiver = receiver0 match {
case Apply(fun1, receiver :: _)
if fun1.symbol == jsdefn.ReflectSelectable_reflectiveSelectable ||
fun1.symbol == jsdefn.Selectable_reflectiveSelectableFromLangReflectiveCalls =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if you could make that conversion method inline to avoid having to deal with it here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried that initially, but if I do that, then suddenly Type Mismatch errors anywhere suggests to import that implicit conversion because it could "make progress towards a solution" ... even though the result type is monomorphically scala.reflect.Selectable, even for type mismatches like "Expected String but found Boolean".

I think the suggestion mechanism assumes that any inline implicit def could return a more precise type than what is declared (because you know, blackbox macros are just whitebox macros with a special body, so nobody can tell from the outside, which is a problem that seems to pop up every now and then in various scenarios).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the suggestion mechanism assumes that any inline implicit def could return a more precise type than what is declared (because you know, blackbox macros are just whitebox macros with a special body, so nobody can tell from the outside, which is a problem that seems to pop up every now and then in various scenarios).

@nicolasstucki What's the current status of whether transparent should be a flag or not? Is this a known issue with the current encoding?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway, for this specific case, I figured it would be better not to have it inline, otherwise it gets sketchy to rely on the fact that the inliner will preserve the shape of the tree, and in particular that the fun1 directly inside the Apply will indeed be the call to reflectiveSelectable (and not a Block that ends in a call to reflectiveSelectable, notably).

genExpr(receiver)

case _ =>
report.error(
"The receiver of Selectable.selectDynamic or Selectable.applyDynamic " +
"must be a call to the (implicit) method scala.reflect.Selectable.reflectiveSelectable. " +
"Other uses are not supported in Scala.js.",
tree.sourcePos)
js.Undefined()
}

// Extract the method name as a String
val methodNameStr = args.head match {
case Literal(Constants.Constant(name: String)) =>
name
case _ =>
report.error(
"The method name given to Selectable.selectDynamic or Selectable.applyDynamic " +
"must be a literal string. " +
"Other uses are not supported in Scala.js.",
args.head.sourcePos)
"erroneous"
}

val (formalParamTypeRefs, actualArgs) = if (isSelectDynamic) {
(Nil, Nil)
} else {
// Extract the param type refs and actual args from the 2nd and 3rd argument to applyDynamic
args.tail match {
case WrapArray(classTagsArray: JavaSeqLiteral) :: WrapArray(actualArgsAnyArray: JavaSeqLiteral) :: Nil =>
// Extract jstpe.Type's and jstpe.TypeRef's from the ClassTag.apply(_) trees
val formalParamTypesAndTypeRefs = classTagsArray.elems.map {
// ClassTag.apply(classOf[tp]) -> tp
case Apply(fun, Literal(const) :: Nil)
if fun.symbol == defn.ClassTagModule_apply && const.tag == Constants.ClazzTag =>
toIRTypeAndTypeRef(const.typeValue)
// ClassTag.SpecialType -> erasure(SepecialType.typeRef) (e.g., ClassTag.Any -> Object)
case Apply(Select(classTagModule, name), Nil)
if classTagModule.symbol == defn.ClassTagModule &&
defn.SpecialClassTagClasses.exists(_.name == name.toTypeName) =>
toIRTypeAndTypeRef(TypeErasure.erasure(
defn.SpecialClassTagClasses.find(_.name == name.toTypeName).get.typeRef))
// Anything else is invalid
case classTag =>
report.error(
"The ClassTags passed to Selectable.applyDynamic must be " +
"literal ClassTag(classOf[T]) expressions " +
"(typically compiler-generated). " +
"Other uses are not supported in Scala.js.",
classTag.sourcePos)
(jstpe.AnyType, jstpe.ClassRef(jsNames.ObjectClass))
}

// Gen the actual args, downcasting them to the formal param types
val actualArgs = actualArgsAnyArray.elems.zip(formalParamTypesAndTypeRefs).map {
(actualArgAny, formalParamTypeAndTypeRef) =>
val genActualArgAny = genExpr(actualArgAny)
js.AsInstanceOf(genActualArgAny, formalParamTypeAndTypeRef._1)(genActualArgAny.pos)
}

(formalParamTypesAndTypeRefs.map(_._2), actualArgs)

case _ =>
report.error(
"Passing the varargs of Selectable.applyDynamic with `: _*` " +
"is not supported in Scala.js.",
Comment on lines +2981 to +2982
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have neg tests for the various error conditions in this method?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. I'm not sure yet how to write negative tests for Scala.js-specific error messages.

tree.sourcePos)
(Nil, Nil)
}
}

val methodName = MethodName.reflectiveProxy(methodNameStr, formalParamTypeRefs)

js.Apply(js.ApplyFlags.empty, receiver, js.MethodIdent(methodName), actualArgs)(jstpe.AnyType)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Judging from the code of reflect.Selectable, selectDynamic is able to select actual bytecode fields (as opposed to accessors). Then again, it falls back to applyDynamic.

I do not know the details of how dotty translates fields, but it seems this is problematic for exact compatibility.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the JVM, we need to look up fields because of Java interop. A Java class such as

public class A {
  public int foo = 5;
}

qualifies for the Scala structural type

{ val foo: Int }

so for Java we need to try fields.

In the SJSIR, however, since JDK classes are implemented in Scala anyway, I don't think there can be an issue.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, OK. Fair enough.

}

/** Gen actual actual arguments to Scala method call.
* Returns a list of the transformed arguments.
*
Expand Down Expand Up @@ -2992,8 +3164,9 @@ class JSCodeGen()(using genCtx: Context) {
lazy val isWrapArray: Set[Symbol] = {
val names0 = defn.ScalaValueClasses().map(sym => nme.wrapXArray(sym.name))
val names1 = names0 ++ Set(nme.wrapRefArray, nme.genericWrapArray)
val names2 = names1.map(defn.ScalaPredefModule.requiredMethod(_))
names2.toSet
val symsInPredef = names1.map(defn.ScalaPredefModule.requiredMethod(_))
val symsInScalaRunTime = names1.map(defn.ScalaRuntimeModule.requiredMethod(_))
(symsInPredef ++ symsInScalaRunTime).toSet
}

def unapply(tree: Apply): Option[Tree] = tree match {
Expand Down
17 changes: 17 additions & 0 deletions compiler/src/dotty/tools/backend/sjs/JSDefinitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,23 @@ final class JSDefinitions()(using Context) {
@threadUnsafe lazy val Reflect_registerInstantiatableClassR = ReflectModule.requiredMethodRef("registerInstantiatableClass")
def Reflect_registerInstantiatableClass(using Context) = Reflect_registerInstantiatableClassR.symbol

@threadUnsafe lazy val ReflectSelectableType: TypeRef = requiredClassRef("scala.reflect.Selectable")
def ReflectSelectableClass(using Context) = ReflectSelectableType.symbol.asClass
@threadUnsafe lazy val ReflectSelectable_selectDynamicR = ReflectSelectableClass.requiredMethodRef("selectDynamic")
def ReflectSelectable_selectDynamic(using Context) = ReflectSelectable_selectDynamicR.symbol
@threadUnsafe lazy val ReflectSelectable_applyDynamicR = ReflectSelectableClass.requiredMethodRef("applyDynamic")
def ReflectSelectable_applyDynamic(using Context) = ReflectSelectable_applyDynamicR.symbol

@threadUnsafe lazy val ReflectSelectableModuleRef = requiredModuleRef("scala.reflect.Selectable")
def ReflectSelectableModule(using Context) = ReflectSelectableModuleRef.symbol
@threadUnsafe lazy val ReflectSelectable_reflectiveSelectableR = ReflectSelectableModule.requiredMethodRef("reflectiveSelectable")
def ReflectSelectable_reflectiveSelectable(using Context) = ReflectSelectable_reflectiveSelectableR.symbol

@threadUnsafe lazy val SelectableModuleRef = requiredModuleRef("scala.Selectable")
def SelectableModule(using Context) = SelectableModuleRef.symbol
@threadUnsafe lazy val Selectable_reflectiveSelectableFromLangReflectiveCallsR = SelectableModule.requiredMethodRef("reflectiveSelectableFromLangReflectiveCalls")
def Selectable_reflectiveSelectableFromLangReflectiveCalls(using Context) = Selectable_reflectiveSelectableFromLangReflectiveCallsR.symbol

private var allRefClassesCache: Set[Symbol] = _
def allRefClasses(using Context): Set[Symbol] = {
if (allRefClassesCache == null) {
Expand Down
9 changes: 8 additions & 1 deletion compiler/src/dotty/tools/backend/sjs/JSEncoding.scala
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,15 @@ object JSEncoding {
}
}

def toIRType(tp: Type)(using Context): jstpe.Type = {
def toIRTypeAndTypeRef(tp: Type)(using Context): (jstpe.Type, jstpe.TypeRef) = {
val typeRefInternal = toTypeRefInternal(tp)
(toIRTypeInternal(typeRefInternal), typeRefInternal._1)
}

def toIRType(tp: Type)(using Context): jstpe.Type =
toIRTypeInternal(toTypeRefInternal(tp))

private def toIRTypeInternal(typeRefInternal: (jstpe.TypeRef, Symbol))(using Context): jstpe.Type = {
typeRefInternal._1 match {
case jstpe.PrimRef(irTpe) =>
irTpe
Expand Down
11 changes: 10 additions & 1 deletion compiler/src/dotty/tools/backend/sjs/JSPrimitives.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ object JSPrimitives {

final val THROW = DEBUGGER + 1

final val LastJSPrimitiveCode = THROW
final val REFLECT_SELECTABLE_SELECTDYN = THROW + 1 // scala.reflect.Selectable.selectDynamic
final val REFLECT_SELECTABLE_APPLYDYN = REFLECT_SELECTABLE_SELECTDYN + 1 // scala.reflect.Selectable.applyDynamic

final val LastJSPrimitiveCode = REFLECT_SELECTABLE_APPLYDYN

def isJSPrimitive(code: Int): Boolean =
code >= FirstJSPrimitiveCode && code <= LastJSPrimitiveCode
Expand All @@ -59,6 +62,9 @@ class JSPrimitives(ictx: Context) extends DottyPrimitives(ictx) {
override def getPrimitive(app: Apply, tpe: Type)(using Context): Int =
jsPrimitives.getOrElse(app.fun.symbol, super.getPrimitive(app, tpe))

override def isPrimitive(sym: Symbol): Boolean =
jsPrimitives.contains(sym) || super.isPrimitive(sym)

override def isPrimitive(fun: Tree): Boolean =
jsPrimitives.contains(fun.symbol(using ictx)) || super.isPrimitive(fun)

Expand Down Expand Up @@ -109,6 +115,9 @@ class JSPrimitives(ictx: Context) extends DottyPrimitives(ictx) {

addPrimitive(defn.throwMethod, THROW)

addPrimitive(jsdefn.ReflectSelectable_selectDynamic, REFLECT_SELECTABLE_SELECTDYN)
addPrimitive(jsdefn.ReflectSelectable_applyDynamic, REFLECT_SELECTABLE_APPLYDYN)

primitives.toMap
}

Expand Down
16 changes: 2 additions & 14 deletions compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1155,20 +1155,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {

/** A tree that corresponds to `Predef.classOf[$tp]` in source */
def clsOf(tp: Type)(using Context): Tree =
if ctx.erasedTypes then
def TYPE(module: TermSymbol) = ref(module).select(nme.TYPE_)
defn.scalaClassName(tp) match
case tpnme.Boolean => TYPE(defn.BoxedBooleanModule)
case tpnme.Byte => TYPE(defn.BoxedByteModule)
case tpnme.Short => TYPE(defn.BoxedShortModule)
case tpnme.Char => TYPE(defn.BoxedCharModule)
case tpnme.Int => TYPE(defn.BoxedIntModule)
case tpnme.Long => TYPE(defn.BoxedLongModule)
case tpnme.Float => TYPE(defn.BoxedFloatModule)
case tpnme.Double => TYPE(defn.BoxedDoubleModule)
case tpnme.Unit => TYPE(defn.BoxedUnitModule)
case _ =>
Literal(Constant(TypeErasure.erasure(tp)))
if ctx.erasedTypes && !tp.isRef(defn.UnitClass) then
Literal(Constant(TypeErasure.erasure(tp)))
else
Literal(Constant(tp))

Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,8 @@ class Definitions {

@tu lazy val NotRuntimeClasses: Set[Symbol] = Set(AnyClass, AnyValClass, NullClass, NothingClass)

@tu lazy val SpecialClassTagClasses: Set[Symbol] = Set(UnitClass, AnyClass, AnyValClass)

/** Classes that are known not to have an initializer irrespective of
* whether NoInits is set. Note: FunctionXXLClass is in this set
* because if it is compiled by Scala2, it does not get a NoInit flag.
Expand Down
7 changes: 2 additions & 5 deletions compiler/src/dotty/tools/dotc/typer/Synthesizer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
val sym = tp.typeSymbol
val classTag = ref(defn.ClassTagModule)
val tag =
if sym == defn.UnitClass
|| sym == defn.AnyClass
|| sym == defn.AnyValClass
then
if defn.SpecialClassTagClasses.contains(sym) then
classTag.select(sym.name.toTermName)
else
classTag.select(nme.apply).appliedToType(tp).appliedTo(clsOf(erasure(tp)))
Expand Down Expand Up @@ -425,4 +422,4 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
EmptyTree
recur(specialHandlers)

end Synthesizer
end Synthesizer
2 changes: 1 addition & 1 deletion project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1059,7 +1059,7 @@ object Build {
++ (dir / "shared/src/test/scala/org/scalajs/testsuite/niocharset" ** (("*.scala": FileFilter) -- "BaseCharsetTest.scala" -- "Latin1Test.scala" -- "USASCIITest.scala" -- "UTF16Test.scala" -- "UTF8Test.scala")).get
++ (dir / "shared/src/test/scala/org/scalajs/testsuite/scalalib" ** (("*.scala": FileFilter) -- "ArrayBuilderTest.scala" -- "ClassTagTest.scala" -- "EnumerationTest.scala" -- "SymbolTest.scala")).get
++ (dir / "shared/src/test/require-sam" ** "*.scala").get
++ (dir / "shared/src/test/require-jdk8/org/scalajs/testsuite/compiler" ** (("*.scala": FileFilter) -- "DefaultMethodsTest.scala")).get
++ (dir / "shared/src/test/require-jdk8/org/scalajs/testsuite/compiler" ** "*.scala").get
++ (dir / "shared/src/test/require-jdk8/org/scalajs/testsuite/javalib/lang" ** "*.scala").get
++ (dir / "shared/src/test/require-jdk8/org/scalajs/testsuite/javalib/util" ** (("*.scala": FileFilter) -- "CollectionsOnCopyOnWriteArrayListTestOnJDK8.scala")).get
++ (dir / "shared/src/test/require-jdk7/org/scalajs/testsuite/javalib/io" ** "*.scala").get
Expand Down
Loading