diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/dotty/tools/backend/sjs/JSCodeGen.scala | 1359 | ||||
-rw-r--r-- | src/dotty/tools/backend/sjs/JSDefinitions.scala | 24 | ||||
-rw-r--r-- | src/dotty/tools/backend/sjs/JSEncoding.scala | 43 | ||||
-rw-r--r-- | src/dotty/tools/backend/sjs/JSInterop.scala | 110 | ||||
-rw-r--r-- | src/dotty/tools/backend/sjs/JSPrimitives.scala | 17 | ||||
-rw-r--r-- | src/dotty/tools/dotc/Compiler.scala | 6 | ||||
-rw-r--r-- | src/dotty/tools/dotc/config/JavaPlatform.scala | 9 | ||||
-rw-r--r-- | src/dotty/tools/dotc/config/Platform.scala | 3 | ||||
-rw-r--r-- | src/dotty/tools/dotc/config/SJSPlatform.scala | 5 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/Phases.scala | 4 | ||||
-rw-r--r-- | src/dotty/tools/dotc/transform/ExpandSAMs.scala | 12 |
11 files changed, 1502 insertions, 90 deletions
diff --git a/src/dotty/tools/backend/sjs/JSCodeGen.scala b/src/dotty/tools/backend/sjs/JSCodeGen.scala index be4e56375..0aa211bc8 100644 --- a/src/dotty/tools/backend/sjs/JSCodeGen.scala +++ b/src/dotty/tools/backend/sjs/JSCodeGen.scala @@ -14,23 +14,23 @@ import dotty.tools.dotc.core._ import Periods._ import SymDenotations._ import Contexts._ +import Decorators._ import Flags._ import dotty.tools.dotc.ast.Trees._ import Types._ import Symbols._ import Denotations._ import Phases._ -import dotty.tools.dotc.util.Positions -import Positions.Position import StdNames._ import dotty.tools.dotc.transform.Erasure import org.scalajs.core.ir -import org.scalajs.core.ir.{ClassKind, Trees => js, Types => jstpe} +import org.scalajs.core.ir.{ClassKind, Position, Trees => js, Types => jstpe} import js.OptimizerHints import JSEncoding._ +import JSInterop._ import ScopedVar.withScopedVars /** Main codegen for Scala.js IR. @@ -76,6 +76,14 @@ class JSCodeGen()(implicit ctx: Context) { private def currentClassType = encodeClassType(currentClassSym) + /** Returns a new fresh local identifier. */ + private def freshLocalIdent()(implicit pos: Position): js.Ident = + localNames.get.freshLocalIdent() + + /** Returns a new fresh local identifier. */ + private def freshLocalIdent(base: String)(implicit pos: Position): js.Ident = + localNames.get.freshLocalIdent(base) + // Compilation unit -------------------------------------------------------- def run(): Unit = { @@ -129,7 +137,7 @@ class JSCodeGen()(implicit ctx: Context) { withScopedVars( currentClassSym := sym ) { - val tree = if (isRawJSType(sym)) { + val tree = if (isJSType(sym)) { /*assert(!isRawJSFunctionDef(sym), s"Raw JS function def should have been recorded: $cd")*/ if (!sym.is(Trait) && isScalaJSDefinedJSClass(sym)) @@ -327,7 +335,23 @@ class JSCodeGen()(implicit ctx: Context) { /** Gen the IR ClassDef for a raw JS class or trait. */ private def genRawJSClassData(td: TypeDef): js.ClassDef = { - ??? + val sym = td.symbol.asClass + implicit val pos: Position = sym.pos + + val classIdent = encodeClassFullNameIdent(sym) + val superClass = + if (sym.is(Trait)) None + else Some(encodeClassFullNameIdent(sym.superClass)) + val jsName = + if (sym.is(Trait) || sym.is(ModuleClass)) None + else Some(fullJSNameOf(sym)) + + js.ClassDef(classIdent, ClassKind.RawJSType, + superClass, + genClassInterfaces(sym), + jsName, + Nil)( + OptimizerHints.empty) } /** Gen the IR ClassDef for an interface definition. @@ -611,7 +635,7 @@ class JSCodeGen()(implicit ctx: Context) { /* Any JavaScript expression is also a statement, but at least we get rid * of some pure expressions that come from our own codegen. */ - implicit val pos: ir.Position = tree.pos + implicit val pos: Position = tree.pos tree match { case js.Block(stats :+ expr) => js.Block(stats :+ exprToStat(expr)) case _:js.Literal | js.This() => js.Skip() @@ -682,22 +706,12 @@ class JSCodeGen()(implicit ctx: Context) { /*case t: Try => genTry(t, isStat)*/ - /*case Throw(expr) => - val ex = genExpr(expr) - js.Throw { - if (isMaybeJavaScriptException(expr.tpe)) { - genApplyMethod( - genLoadModule(RuntimePackageModule), - Runtime_unwrapJavaScriptException, - List(ex)) - } else { - ex - } - }*/ - case app: Apply => genApply(app, isStat) + case app: TypeApply => + genTypeApply(app) + /*case app: ApplyDynamic => genApplyDynamic(app)*/ @@ -831,23 +845,22 @@ class JSCodeGen()(implicit ctx: Context) { } /** Array constructor */ - /*case av: ArrayValue => - genArrayValue(av) + case javaSeqLiteral: JavaSeqLiteral => + genJavaSeqLiteral(javaSeqLiteral) /** A Match reaching the backend is supposed to be optimized as a switch */ - case mtch: Match => - genMatch(mtch, isStat) + /*case mtch: Match => + genMatch(mtch, isStat)*/ - /** Anonymous function (only with -Ydelambdafy:method) */ - case fun: Function => - genAnonFunction(fun) + case tree: Closure => + genClosure(tree) - case EmptyTree => + /*case EmptyTree => js.Skip()*/ case _ => throw new FatalError("Unexpected tree in genExpr: " + - tree + "/" + tree.getClass + " at: " + tree.pos) + tree + "/" + tree.getClass + " at: " + (tree.pos: Position)) } } // end of genStatOrExpr() @@ -866,6 +879,18 @@ class JSCodeGen()(implicit ctx: Context) { } } + private def qualifierOf(fun: Tree): Tree = fun match { + case fun: Ident => + fun.tpe match { + case TermRef(prefix: TermRef, _) => tpd.ref(prefix) + case TermRef(prefix: ThisType, _) => tpd.This(prefix.cls) + } + case Select(qualifier, _) => + qualifier + case TypeApply(fun, _) => + qualifierOf(fun) + } + /** Gen JS this of the current class. * Normally encoded straightforwardly as a JS this. * But must be replaced by the `thisLocalVarIdent` local variable if there @@ -900,35 +925,22 @@ class JSCodeGen()(implicit ctx: Context) { case fun => fun } - def isRawJSDefaultParam: Boolean = { - false /* - if (isCtorDefaultParam(sym)) { - isRawJSCtorDefaultParam(sym) - } else { - sym.hasFlag(reflect.internal.Flags.DEFAULTPARAM) && - isRawJSType(sym.owner.tpe) - }*/ - } - fun match { - /*case _: TypeApply => - genApplyTypeApply(tree)*/ - - /*case _ if isRawJSDefaultParam => - js.UndefinedParam()(toIRType(sym.tpe.resultType))*/ + case _ if isJSDefaultParam(sym) => + js.UndefinedParam()(toIRType(sym.info.finalResultType)) case Select(Super(_, _), _) => genSuperCall(tree, isStat) - /*case Select(New(_), nme.CONSTRUCTOR) => - genApplyNew(tree)*/ + case Select(New(_), nme.CONSTRUCTOR) => + genApplyNew(tree) case _ => /*if (sym.isLabel) { genLabelApply(tree) - } else if (primitives.isPrimitive(tree)) { + } else*/ if (primitives.isPrimitive(tree)) { genPrimitiveOp(tree, isStat) - } else*/ if (Erasure.Boxing.isBox(sym)) { + } else if (Erasure.Boxing.isBox(sym)) { // Box a primitive value (cannot be Unit) val arg = args.head makePrimitiveBox(genExpr(arg), arg.tpe) @@ -979,6 +991,607 @@ class JSCodeGen()(implicit ctx: Context) { } } + /** Gen JS code for a constructor call (new). + * Further refined into: + * * new String(...) + * * new of a hijacked boxed class + * * new of an anonymous function class that was recorded as JS function + * * new of a raw JS class + * * new Array + * * regular new + */ + private def genApplyNew(tree: Apply): js.Tree = { + implicit val pos: Position = tree.pos + + val Apply(fun @ Select(New(tpt), nme.CONSTRUCTOR), args) = tree + val ctor = fun.symbol + val tpe = tpt.tpe + + assert(ctor.isClassConstructor, + "'new' call to non-constructor: " + ctor.name) + + if (tpe.isRef(defn.StringClass)) { + genNewString(ctor, genActualArgs(ctor, args)) + } else /*if (isHijackedBoxedClass(tpe.typeSymbol)) { + genNewHijackedBoxedClass(tpe.typeSymbol, ctor, args map genExpr) + } else if (translatedAnonFunctions contains tpe.typeSymbol) { + val functionMaker = translatedAnonFunctions(tpe.typeSymbol) + functionMaker(args map genExpr) + } else*/ if (isJSType(tpe.widenDealias.typeSymbol)) { + val clsSym = tpe.widenDealias.typeSymbol + if (clsSym == jsdefn.JSObjectClass && args.isEmpty) js.JSObjectConstr(Nil) + else if (clsSym == jsdefn.JSArrayClass && args.isEmpty) js.JSArrayConstr(Nil) + else js.JSNew(genLoadJSConstructor(clsSym), genActualJSArgs(ctor, args)) + } else { + toIRType(tpe) match { + case cls: jstpe.ClassType => + js.New(cls, encodeMethodSym(ctor), genActualArgs(ctor, args)) + + case other => + throw new FatalError(s"Non ClassType cannot be instantiated: $other") + } + } + } + + /** Gen JS code for a primitive method call. */ + private def genPrimitiveOp(tree: Apply, isStat: Boolean): js.Tree = { + import scala.tools.nsc.backend.ScalaPrimitives._ + + implicit val pos: Position = tree.pos + + val Apply(fun, args) = tree + val receiver = qualifierOf(fun) + + val code = primitives.getPrimitive(tree, receiver.tpe) + + if (isArithmeticOp(code) || isLogicalOp(code) || isComparisonOp(code)) + genSimpleOp(tree, receiver :: args, code) + else if (code == CONCAT) + genStringConcat(tree, receiver, args) + else if (code == HASH) + genScalaHash(tree, receiver) + else if (isArrayNew(code)) + genArrayNew(tree, code) + else if (isArrayOp(code)) + genArrayOp(tree, code) + else if (code == SYNCHRONIZED) + genSynchronized(tree, isStat) + else if (isCoercion(code)) + genCoercion(tree, receiver, code) + else if (code == JSPrimitives.THROW) + genThrow(tree, args) + else /*if (primitives.isJSPrimitive(code)) + genJSPrimitive(tree, receiver, args, code) + else*/ + throw new FatalError(s"Unknown primitive: ${tree.symbol.fullName} at: $pos") + } + + /** Gen JS code for a simple operation (arithmetic, logical, or comparison) */ + private def genSimpleOp(tree: Apply, args: List[Tree], code: Int): js.Tree = { + args match { + case List(arg) => genSimpleUnaryOp(tree, arg, code) + case List(lhs, rhs) => genSimpleBinaryOp(tree, lhs, rhs, code) + case _ => throw new FatalError("Incorrect arity for primitive") + } + } + + /** Gen JS code for a simple unary operation. */ + private def genSimpleUnaryOp(tree: Apply, arg: Tree, code: Int): js.Tree = { + import scala.tools.nsc.backend.ScalaPrimitives._ + + implicit val pos: Position = tree.pos + + val genArg = genExpr(arg) + val resultIRType = toIRType(tree.tpe) + + (code: @switch) match { + case POS => + genArg + + case NEG => + (resultIRType: @unchecked) match { + case jstpe.IntType => + js.BinaryOp(js.BinaryOp.Int_-, js.IntLiteral(0), genArg) + case jstpe.LongType => + js.BinaryOp(js.BinaryOp.Long_-, js.LongLiteral(0), genArg) + case jstpe.FloatType => + js.BinaryOp(js.BinaryOp.Float_-, js.FloatLiteral(0.0f), genArg) + case jstpe.DoubleType => + js.BinaryOp(js.BinaryOp.Double_-, js.DoubleLiteral(0), genArg) + } + + case NOT => + (resultIRType: @unchecked) match { + case jstpe.IntType => + js.BinaryOp(js.BinaryOp.Int_^, js.IntLiteral(-1), genArg) + case jstpe.LongType => + js.BinaryOp(js.BinaryOp.Long_^, js.LongLiteral(-1), genArg) + } + + case ZNOT => + js.UnaryOp(js.UnaryOp.Boolean_!, genArg) + + case _ => + throw new FatalError("Unknown unary operation code: " + code) + } + } + + /** Gen JS code for a simple binary operation. */ + private def genSimpleBinaryOp(tree: Apply, lhs: Tree, rhs: Tree, code: Int): js.Tree = { + import scala.tools.nsc.backend.ScalaPrimitives._ + import js.UnaryOp._ + + /* Codes for operation types, in an object so that they can be 'final val' + * and be used in switch-matches. + */ + object OpTypes { + final val DoubleOp = 1 + final val FloatOp = 2 + final val LongOp = 3 + final val IntOp = 4 + final val BooleanOp = 5 + final val AnyOp = 6 + } + import OpTypes._ + + implicit val pos: Position = tree.pos + + val lhsIRType = toIRType(lhs.tpe) + val rhsIRType = toIRType(rhs.tpe) + + val opType = (lhsIRType, rhsIRType) match { + case (jstpe.DoubleType, _) | (_, jstpe.DoubleType) => DoubleOp + case (jstpe.FloatType, _) | (_, jstpe.FloatType) => FloatOp + case (jstpe.LongType, _) | (_, jstpe.LongType) => LongOp + case (jstpe.IntType, _) | (_, jstpe.IntType) => IntOp + case (jstpe.BooleanType, jstpe.BooleanType) => BooleanOp + case _ => AnyOp + } + + if (opType == AnyOp && isUniversalEqualityOp(code)) { + genUniversalEqualityOp(lhs, rhs, code) + } else if (code == ZOR) { + js.If(genExpr(lhs), js.BooleanLiteral(true), genExpr(rhs))(jstpe.BooleanType) + } else if (code == ZAND) { + js.If(genExpr(lhs), genExpr(rhs), js.BooleanLiteral(false))(jstpe.BooleanType) + } else { + import js.BinaryOp._ + + def coerce(tree: js.Tree, opType: Int): js.Tree = (opType: @switch) match { + case DoubleOp => + if (tree.tpe == jstpe.LongType) js.UnaryOp(LongToDouble, tree) + else tree + + case FloatOp => + if (tree.tpe == jstpe.FloatType || tree.tpe == jstpe.IntType) tree + else js.UnaryOp(DoubleToFloat, coerce(tree, DoubleOp)) + + case LongOp => + if (tree.tpe == jstpe.LongType) tree + else { + assert(tree.tpe == jstpe.IntType) + js.UnaryOp(IntToLong, tree) + } + + case IntOp => + if (tree.tpe == jstpe.IntType) tree + else { + assert(tree.tpe == jstpe.LongType) + js.UnaryOp(LongToInt, tree) + } + + case BooleanOp | AnyOp => + tree + } + + val rhsOpType = code match { + case LSL | LSR | ASR => IntOp + case _ => opType + } + + val genLhs = coerce(genExpr(lhs), opType) + val genRhs = coerce(genExpr(rhs), rhsOpType) + + val op = (opType: @switch) match { + case IntOp => + (code: @switch) match { + case ADD => Int_+ + case SUB => Int_- + case MUL => Int_* + case DIV => Int_/ + case MOD => Int_% + case OR => Int_| + case AND => Int_& + case XOR => Int_^ + case LSL => Int_<< + case LSR => Int_>>> + case ASR => Int_>> + + case EQ => Num_== + case NE => Num_!= + case LT => Num_< + case LE => Num_<= + case GT => Num_> + case GE => Num_>= + } + + case FloatOp => + (code: @switch) match { + case ADD => Float_+ + case SUB => Float_- + case MUL => Float_* + case DIV => Float_/ + case MOD => Float_% + + case EQ => Num_== + case NE => Num_!= + case LT => Num_< + case LE => Num_<= + case GT => Num_> + case GE => Num_>= + } + + case DoubleOp => + (code: @switch) match { + case ADD => Double_+ + case SUB => Double_- + case MUL => Double_* + case DIV => Double_/ + case MOD => Double_% + + case EQ => Num_== + case NE => Num_!= + case LT => Num_< + case LE => Num_<= + case GT => Num_> + case GE => Num_>= + } + + case LongOp => + (code: @switch) match { + case ADD => Long_+ + case SUB => Long_- + case MUL => Long_* + case DIV => Long_/ + case MOD => Long_% + case OR => Long_| + case XOR => Long_^ + case AND => Long_& + case LSL => Long_<< + case LSR => Long_>>> + case ASR => Long_>> + + case EQ => Long_== + case NE => Long_!= + case LT => Long_< + case LE => Long_<= + case GT => Long_> + case GE => Long_>= + } + + case BooleanOp => + (code: @switch) match { + case EQ => Boolean_== + case NE => Boolean_!= + case OR => Boolean_| + case AND => Boolean_& + case XOR => Boolean_!= + } + + case AnyOp => + /* No @switch because some 2.11 version erroneously report a warning + * for switches with less than 3 non-default cases. + */ + code match { + case ID => === + case NI => !== + } + } + + js.BinaryOp(op, genLhs, genRhs) + } + } + + /** Gen JS code for a universal equality test. */ + private def genUniversalEqualityOp(lhs: Tree, rhs: Tree, code: Int)( + implicit pos: Position): js.Tree = { + + import scala.tools.nsc.backend.ScalaPrimitives._ + + val genLhs = genExpr(lhs) + val genRhs = genExpr(rhs) + + val bypassEqEq = { + // Do not call equals if we have a literal null at either side. + genLhs.isInstanceOf[js.Null] || + genRhs.isInstanceOf[js.Null] + } + + if (bypassEqEq) { + js.BinaryOp( + if (code == EQ) js.BinaryOp.=== else js.BinaryOp.!==, + genLhs, genRhs) + } else { + val body = genEqEqPrimitive(lhs.tpe, rhs.tpe, genLhs, genRhs) + if (code == EQ) body + else js.UnaryOp(js.UnaryOp.Boolean_!, body) + } + } + + private lazy val externalEqualsNumNum: Symbol = + defn.BoxesRunTimeModule.requiredMethod(nme.equalsNumNum) + private lazy val externalEqualsNumChar: Symbol = + NoSymbol // ctx.requiredMethod(BoxesRunTimeTypeRef, nme.equalsNumChar) // this method is private + private lazy val externalEqualsNumObject: Symbol = + defn.BoxesRunTimeModule.requiredMethod(nme.equalsNumObject) + private lazy val externalEquals: Symbol = + defn.BoxesRunTimeClass.info.decl(nme.equals_).suchThat(toDenot(_).info.firstParamTypes.size == 2).symbol + + /** Gen JS code for a call to Any.== */ + private def genEqEqPrimitive(ltpe: Type, rtpe: Type, lsrc: js.Tree, rsrc: js.Tree)( + implicit pos: Position): js.Tree = { + ctx.debuglog(s"$ltpe == $rtpe") + val lsym = ltpe.widenDealias.typeSymbol.asClass + val rsym = rtpe.widenDealias.typeSymbol.asClass + + /* True if the equality comparison is between values that require the + * use of the rich equality comparator + * (scala.runtime.BoxesRunTime.equals). + * This is the case when either side of the comparison might have a + * run-time type subtype of java.lang.Number or java.lang.Character, + * **which includes when either is a JS type**. + * When it is statically known that both sides are equal and subtypes of + * Number or Character, not using the rich equality is possible (their + * own equals method will do ok.) + */ + val mustUseAnyComparator: Boolean = { + isJSType(lsym) || isJSType(rsym) || { + val p = ctx.platform + val areSameFinals = lsym.is(Final) && rsym.is(Final) && (ltpe =:= rtpe) + !areSameFinals && p.isMaybeBoxed(lsym) && p.isMaybeBoxed(rsym) + } + } + + if (mustUseAnyComparator) { + val equalsMethod: Symbol = { + // scalastyle:off line.size.limit + val ptfm = ctx.platform + if (lsym.derivesFrom(defn.BoxedNumberClass)) { + if (rsym.derivesFrom(defn.BoxedNumberClass)) externalEqualsNumNum + else if (rsym.derivesFrom(defn.BoxedCharClass)) externalEqualsNumObject // will be externalEqualsNumChar in 2.12, SI-9030 + else externalEqualsNumObject + } else externalEquals + // scalastyle:on line.size.limit + } + genModuleApplyMethod(equalsMethod, List(lsrc, rsrc)) + } else { + // if (lsrc eq null) rsrc eq null else lsrc.equals(rsrc) + if (lsym == defn.StringClass) { + // String.equals(that) === (this eq that) + js.BinaryOp(js.BinaryOp.===, lsrc, rsrc) + } else { + /* This requires to evaluate both operands in local values first. + * The optimizer will eliminate them if possible. + */ + val ltemp = js.VarDef(freshLocalIdent(), lsrc.tpe, mutable = false, lsrc) + val rtemp = js.VarDef(freshLocalIdent(), rsrc.tpe, mutable = false, rsrc) + js.Block( + ltemp, + rtemp, + js.If(js.BinaryOp(js.BinaryOp.===, ltemp.ref, js.Null()), + js.BinaryOp(js.BinaryOp.===, rtemp.ref, js.Null()), + genApplyMethod(ltemp.ref, defn.Any_equals, List(rtemp.ref)))( + jstpe.BooleanType)) + } + } + } + + /** Gen JS code for string concatenation. + */ + private def genStringConcat(tree: Apply, receiver: Tree, + args: List[Tree]): js.Tree = { + implicit val pos: Position = tree.pos + + val arg = args.head + + /* Primitive number types such as scala.Int have a + * def +(s: String): String + * method, which is why we have to box the lhs sometimes. + * Otherwise, both lhs and rhs are already reference types (Any or String) + * so boxing is not necessary (in particular, rhs is never a primitive). + */ + assert(!isPrimitiveValueType(receiver.tpe) || arg.tpe.isRef(defn.StringClass)) + assert(!isPrimitiveValueType(arg.tpe)) + + val genLhs = { + val genLhs0 = genExpr(receiver) + // Box the receiver if it is a primitive value + if (!isPrimitiveValueType(receiver.tpe)) genLhs0 + else makePrimitiveBox(genLhs0, receiver.tpe) + } + + val genRhs = genExpr(arg) + + js.BinaryOp(js.BinaryOp.String_+, genLhs, genRhs) + } + + /** Gen JS code for a call to Any.## */ + private def genScalaHash(tree: Apply, receiver: Tree): js.Tree = { + implicit val pos: Position = tree.pos + + genModuleApplyMethod(defn.ScalaRuntimeModule.requiredMethod(nme.hash_), + List(genExpr(receiver))) + } + + /** Gen JS code for a new array operation. */ + private def genArrayNew(tree: Tree, code: Int): js.Tree = { + import scala.tools.nsc.backend.ScalaPrimitives._ + + implicit val pos: Position = tree.pos + + val Apply(fun, args) = tree + val genLength = genExpr(args.head) + + toIRType(tree.tpe) match { + case arrayType: jstpe.ArrayType => + js.NewArray(arrayType, List(genLength)) + + case irTpe => + throw new FatalError(s"ArrayNew $tree must have an array type but was $irTpe") + } + } + + /** Gen JS code for an array operation (get, set or length) */ + private def genArrayOp(tree: Tree, code: Int): js.Tree = { + import scala.tools.nsc.backend.ScalaPrimitives._ + + implicit val pos: Position = tree.pos + + val Apply(fun, args) = tree + val arrayObj = qualifierOf(fun) + + val genArray = genExpr(arrayObj) + val genArgs = args.map(genExpr) + + def elementType: Type = arrayObj.tpe.widenDealias match { + case defn.ArrayOf(el) => el + case JavaArrayType(el) => el + case tpe => + ctx.error(s"expected Array $tpe") + ErrorType + } + + def genSelect(): js.Tree = + js.ArraySelect(genArray, genArgs(0))(toIRType(elementType)) + + if (isArrayGet(code)) { + // get an item of the array + assert(args.length == 1, + s"Array get requires 1 argument, found ${args.length} in $tree") + genSelect() + } else if (isArraySet(code)) { + // set an item of the array + assert(args.length == 2, + s"Array set requires 2 arguments, found ${args.length} in $tree") + js.Assign(genSelect(), genArgs(1)) + } else { + // length of the array + js.ArrayLength(genArray) + } + } + + /** Gen JS code for a call to AnyRef.synchronized */ + private def genSynchronized(tree: Apply, isStat: Boolean): js.Tree = { + /* JavaScript is single-threaded, so we can drop the + * synchronization altogether. + */ + val Apply(fun, List(arg)) = tree + val receiver = qualifierOf(fun) + + val genReceiver = genExpr(receiver) + val genArg = genStatOrExpr(arg, isStat) + + genReceiver match { + case js.This() => + // common case for which there is no side-effect nor NPE + genArg + case _ => + implicit val pos: Position = tree.pos + /* TODO Check for a null receiver? + * In theory, it's UB, but that decision should be left for link time. + */ + js.Block(genReceiver, genArg) + } + } + + /** Gen JS code for a coercion */ + private def genCoercion(tree: Apply, receiver: Tree, code: Int): js.Tree = { + import scala.tools.nsc.backend.ScalaPrimitives._ + + implicit val pos: Position = tree.pos + + val source = genExpr(receiver) + + def source2int = (code: @switch) match { + case F2C | D2C | F2B | D2B | F2S | D2S | F2I | D2I => + js.UnaryOp(js.UnaryOp.DoubleToInt, source) + case L2C | L2B | L2S | L2I => + js.UnaryOp(js.UnaryOp.LongToInt, source) + case _ => + source + } + + (code: @switch) match { + // To Char, need to crop at unsigned 16-bit + case B2C | S2C | I2C | L2C | F2C | D2C => + js.BinaryOp(js.BinaryOp.Int_&, source2int, js.IntLiteral(0xffff)) + + // To Byte, need to crop at signed 8-bit + case C2B | S2B | I2B | L2B | F2B | D2B => + // note: & 0xff would not work because of negative values + js.BinaryOp(js.BinaryOp.Int_>>, + js.BinaryOp(js.BinaryOp.Int_<<, source2int, js.IntLiteral(24)), + js.IntLiteral(24)) + + // To Short, need to crop at signed 16-bit + case C2S | I2S | L2S | F2S | D2S => + // note: & 0xffff would not work because of negative values + js.BinaryOp(js.BinaryOp.Int_>>, + js.BinaryOp(js.BinaryOp.Int_<<, source2int, js.IntLiteral(16)), + js.IntLiteral(16)) + + // To Int, need to crop at signed 32-bit + case L2I | F2I | D2I => + source2int + + // Any int to Long + case C2L | B2L | S2L | I2L => + js.UnaryOp(js.UnaryOp.IntToLong, source) + + // Any double to Long + case F2L | D2L => + js.UnaryOp(js.UnaryOp.DoubleToLong, source) + + // Long to Double + case L2D => + js.UnaryOp(js.UnaryOp.LongToDouble, source) + + // Any int, or Double, to Float + case C2F | B2F | S2F | I2F | D2F => + js.UnaryOp(js.UnaryOp.DoubleToFloat, source) + + // Long to Float === Long to Double to Float + case L2F => + js.UnaryOp(js.UnaryOp.DoubleToFloat, + js.UnaryOp(js.UnaryOp.LongToDouble, source)) + + // Identities and IR upcasts + case C2C | B2B | S2S | I2I | L2L | F2F | D2D | + C2I | C2D | + B2S | B2I | B2D | + S2I | S2D | + I2D | + F2D => + source + } + } + + /** Gen a call to the special `throw` method. */ + private def genThrow(tree: Apply, args: List[Tree]): js.Tree = { + implicit val pos: Position = tree.pos + val exception = args.head + val genException = genExpr(exception) + js.Throw { + if (exception.tpe.widenDealias.typeSymbol.derivesFrom(jsdefn.JavaScriptExceptionClass)) { + genModuleApplyMethod( + jsdefn.RuntimePackage_unwrapJavaScriptException, + List(genException)) + } else { + genException + } + } + } + /** Gen a "normal" apply (to a true method). * * But even these are further refined into: @@ -1004,14 +1617,14 @@ class JSCodeGen()(implicit ctx: Context) { case _ => false } - /*if (sym.owner == defn.StringClass && !isStringMethodFromObject) { - genStringCall(tree) - } else if (isRawJSType(sym.owner)) { - if (!isScalaJSDefinedJSClass(sym.owner) || isExposed(sym)) - genPrimitiveJSCall(tree, isStat) - else - genApplyJSClassMethod(genExpr(receiver), sym, genActualArgs(sym, args)) - } else*/ if (foreignIsImplClass(sym.owner)) { + if (sym.owner == defn.StringClass && !isStringMethodFromObject) { + genApplyMethodOfString(genExpr(receiver), sym, genActualArgs(sym, args)) + } else if (isJSType(sym.owner)) { + //if (!isScalaJSDefinedJSClass(sym.owner) || isExposed(sym)) + genApplyJSMethodGeneric(tree, sym, genExpr(receiver), genActualJSArgs(sym, args), isStat) + /*else + genApplyJSClassMethod(genExpr(receiver), sym, genActualArgs(sym, args))*/ + } else if (foreignIsImplClass(sym.owner)) { genTraitImplApply(sym, args.map(genExpr)) } else if (sym.isClassConstructor) { // Calls to constructors are always statically linked @@ -1021,6 +1634,414 @@ class JSCodeGen()(implicit ctx: Context) { } } + /** Gen JS code for a call to a JS method (of a subclass of `js.Any`). + * + * Basically it boils down to calling the method as a `JSBracketSelect`, + * without name mangling. But other aspects come into play: + * + * - Operator methods are translated to JS operators (not method calls) + * - `apply` is translated as a function call, i.e., `o()` instead of `o.apply()` + * - Scala varargs are turned into JS varargs (see `genPrimitiveJSArgs()`) + * - Getters and parameterless methods are translated as `JSBracketSelect` + * - Setters are translated to `Assign` to `JSBracketSelect` + */ + private def genApplyJSMethodGeneric(tree: Tree, sym: Symbol, + receiver: js.Tree, args: List[js.Tree], isStat: Boolean, + superIn: Option[Symbol] = None)( + implicit pos: Position): js.Tree = { + + implicit val pos: Position = tree.pos + + def noSpread = !args.exists(_.isInstanceOf[js.JSSpread]) + val argc = args.size // meaningful only for methods that don't have varargs + + def requireNotSuper(): Unit = { + if (superIn.isDefined) + ctx.error("Illegal super call in Scala.js-defined JS class", tree.pos) + } + + def hasExplicitJSEncoding = { + sym.hasAnnotation(jsdefn.JSNameAnnot) || + sym.hasAnnotation(jsdefn.JSBracketAccessAnnot) || + sym.hasAnnotation(jsdefn.JSBracketCallAnnot) + } + + val boxedResult = sym.name match { + case JSUnaryOpMethodName(code) if argc == 0 => + requireNotSuper() + js.JSUnaryOp(code, receiver) + + case JSBinaryOpMethodName(code) if argc == 1 => + requireNotSuper() + js.JSBinaryOp(code, receiver, args.head) + + case nme.apply if !hasExplicitJSEncoding => + requireNotSuper() + if (jsdefn.isJSThisFunctionClass(sym.owner)) + js.JSBracketMethodApply(receiver, js.StringLiteral("call"), args) + else + js.JSFunctionApply(receiver, args) + + case _ => + def jsFunName = js.StringLiteral(jsNameOf(sym)) + + def genSuperReference(propName: js.Tree): js.Tree = { + superIn.fold[js.Tree] { + js.JSBracketSelect(receiver, propName) + } { superInSym => + js.JSSuperBracketSelect( + jstpe.ClassType(encodeClassFullName(superInSym)), + receiver, propName) + } + } + + def genSelectGet(propName: js.Tree): js.Tree = + genSuperReference(propName) + + def genSelectSet(propName: js.Tree, value: js.Tree): js.Tree = + js.Assign(genSuperReference(propName), value) + + def genCall(methodName: js.Tree, args: List[js.Tree]): js.Tree = { + superIn.fold[js.Tree] { + js.JSBracketMethodApply( + receiver, methodName, args) + } { superInSym => + js.JSSuperBracketCall( + jstpe.ClassType(encodeClassFullName(superInSym)), + receiver, methodName, args) + } + } + + if (isJSGetter(sym)) { + assert(noSpread && argc == 0) + genSelectGet(jsFunName) + } else if (isJSSetter(sym)) { + assert(noSpread && argc == 1) + genSelectSet(jsFunName, args.head) + } else if (isJSBracketAccess(sym)) { + assert(noSpread && (argc == 1 || argc == 2), + s"@JSBracketAccess methods should have 1 or 2 non-varargs arguments") + args match { + case List(keyArg) => + genSelectGet(keyArg) + case List(keyArg, valueArg) => + genSelectSet(keyArg, valueArg) + } + } else if (isJSBracketCall(sym)) { + val (methodName, actualArgs) = extractFirstArg(args) + genCall(methodName, actualArgs) + } else { + genCall(jsFunName, args) + } + } + + if (isStat) { + boxedResult + } else { + val tpe = ctx.atPhase(ctx.elimErasedValueTypePhase) { implicit ctx => + sym.info.finalResultType + } + unbox(boxedResult, tpe) + } + } + + private object JSUnaryOpMethodName { + private val map = Map( + nme.UNARY_+ -> js.JSUnaryOp.+, + nme.UNARY_- -> js.JSUnaryOp.-, + nme.UNARY_~ -> js.JSUnaryOp.~, + nme.UNARY_! -> js.JSUnaryOp.! + ) + + def unapply(name: Names.TermName): Option[js.JSUnaryOp.Code] = + map.get(name) + } + + private object JSBinaryOpMethodName { + private val map = Map( + nme.ADD -> js.JSBinaryOp.+, + nme.SUB -> js.JSBinaryOp.-, + nme.MUL -> js.JSBinaryOp.*, + nme.DIV -> js.JSBinaryOp./, + nme.MOD -> js.JSBinaryOp.%, + + nme.LSL -> js.JSBinaryOp.<<, + nme.ASR -> js.JSBinaryOp.>>, + nme.LSR -> js.JSBinaryOp.>>>, + nme.OR -> js.JSBinaryOp.|, + nme.AND -> js.JSBinaryOp.&, + nme.XOR -> js.JSBinaryOp.^, + + nme.LT -> js.JSBinaryOp.<, + nme.LE -> js.JSBinaryOp.<=, + nme.GT -> js.JSBinaryOp.>, + nme.GE -> js.JSBinaryOp.>=, + + nme.ZAND -> js.JSBinaryOp.&&, + nme.ZOR -> js.JSBinaryOp.|| + ) + + def unapply(name: Names.TermName): Option[js.JSBinaryOp.Code] = + map.get(name) + } + + /** Extract the first argument in a list of actual arguments. + * + * This is nothing else than decomposing into head and tail, except that + * we assert that the first element is not a JSSpread. + */ + private def extractFirstArg(args: List[js.Tree]): (js.Tree, List[js.Tree]) = { + assert(args.nonEmpty, + "Trying to extract the first argument of an empty argument list") + val firstArg = args.head + assert(!firstArg.isInstanceOf[js.JSSpread], + "Trying to extract the first argument of an argument list starting " + + "with a Spread argument: " + firstArg) + (firstArg, args.tail) + } + + /** Gen JS code for a call to a polymorphic method. + * + * The only methods that reach the back-end as polymorphic are + * `isInstanceOf` and `asInstanceOf`. + * + * (Well, in fact `DottyRunTime.newRefArray` too, but it is handled as a + * primitive instead.) + */ + private def genTypeApply(tree: TypeApply): js.Tree = { + implicit val pos: Position = tree.pos + + val TypeApply(fun, targs) = tree + + val sym = fun.symbol + val receiver = qualifierOf(fun) + + val to = targs.head.tpe + + assert(!isPrimitiveValueType(receiver.tpe), + s"Found receiver of type test with primitive type ${receiver.tpe} at $pos") + assert(!isPrimitiveValueType(to), + s"Found target type of type test with primitive type ${receiver.tpe} at $pos") + + val genReceiver = genExpr(receiver) + + if (sym == defn.Any_asInstanceOf) { + genAsInstanceOf(genReceiver, to) + } else if (sym == defn.Any_isInstanceOf) { + genIsInstanceOf(tree, genReceiver, to) + } else { + throw new FatalError( + s"Unexpected type application $fun with symbol ${sym.fullName}") + } + } + + /** Gen JS code for a Java Seq literal. */ + private def genJavaSeqLiteral(tree: JavaSeqLiteral): js.Tree = { + implicit val pos: Position = tree.pos + + val genElems = tree.elems.map(genExpr) + val arrayType = toReferenceType(tree.tpe).asInstanceOf[jstpe.ArrayType] + js.ArrayValue(arrayType, genElems) + } + + /** Gen JS code for a closure. + * + * Input: a `Closure` tree of the form + * {{{ + * Closure(env, call, functionalInterface) + * }}} + * representing the pseudo-syntax + * {{{ + * { (p1, ..., pm) => call(env1, ..., envn, p1, ..., pm) }: functionInterface + * }}} + * where `envi` are identifiers in the local scope. The qualifier of `call` + * is also implicitly captured. + * + * Output: a `js.Closure` tree of the form + * {{{ + * js.Closure(formalCaptures, formalParams, body, actualCaptures) + * }}} + * representing the pseudo-syntax + * {{{ + * lambda<formalCapture1 = actualCapture1, ..., formalCaptureN = actualCaptureN>( + * formalParam1, ..., formalParamM) = body + * }}} + * where the `actualCaptures` and `body` are, in general, arbitrary + * expressions. But in this case, `actualCaptures` will be identifiers from + * `env`, and the `body` will be of the form + * {{{ + * call(formalCapture1.ref, ..., formalCaptureN.ref, + * formalParam1.ref, ...formalParamM.ref) + * }}} + * + * When the `js.Closure` node is evaluated, i.e., when the closure value is + * created, the expressions of the `actualCaptures` are evaluated, and the + * results of those evaluations is "stored" in the environment of the + * closure as the corresponding `formalCapture`. + * + * When we later *call* the closure, the `formalCaptures` already have their + * values from the environment, and they are available in the `body`. The + * `formalParams` of the created closure receive their values from the + * actual arguments at the call-site of the closure, and they are also + * available in the `body`. + */ + private def genClosure(tree: Closure): js.Tree = { + implicit val pos: Position = tree.pos + val Closure(env, call, functionalInterface) = tree + + val envSize = env.size + + val (fun, args) = call match { + // case Apply(fun, args) => (fun, args) // Conjectured not to happen + case t @ Select(_, _) => (t, Nil) + case t @ Ident(_) => (t, Nil) + } + val sym = fun.symbol + + val qualifier = qualifierOf(fun) + val allCaptureValues = qualifier :: env + + val (formalCaptures, actualCaptures) = allCaptureValues.map { value => + implicit val pos: Position = value.pos + val formalIdent = value match { + case Ident(name) => freshLocalIdent(name.toString) + case This(_) => freshLocalIdent("this") + case _ => freshLocalIdent() + } + val formalCapture = + js.ParamDef(formalIdent, toIRType(value.tpe), mutable = false, rest = false) + val actualCapture = genExpr(value) + (formalCapture, actualCapture) + }.unzip + + val formalParamNames = sym.info.paramNamess.flatten.drop(envSize) + val formalParamTypes = sym.info.paramTypess.flatten.drop(envSize) + val (formalParams, actualParams) = formalParamNames.zip(formalParamTypes).map { + case (name, tpe) => + val formalParam = js.ParamDef(freshLocalIdent(name.toString), + jstpe.AnyType, mutable = false, rest = false) + val actualParam = unbox(formalParam.ref, tpe) + (formalParam, actualParam) + }.unzip + + val genBody = { + val thisCaptureRef :: argCaptureRefs = formalCaptures.map(_.ref) + val call = genApplyMethod(thisCaptureRef, sym, argCaptureRefs ::: actualParams) + box(call, sym.info.finalResultType) + } + + val closure = js.Closure(formalCaptures, formalParams, genBody, actualCaptures) + ctx.debuglog(closure.toString) + + val funInterfaceSym = functionalInterface.tpe.widenDealias.typeSymbol + if (jsdefn.isJSFunctionClass(funInterfaceSym)) { + closure + } else { + assert(!funInterfaceSym.exists || defn.isFunctionClass(funInterfaceSym), + s"Invalid functional interface $funInterfaceSym reached the back-end") + val cls = "sjsr_AnonFunction" + formalParams.size + val ctor = js.Ident("init___sjs_js_Function" + formalParams.size) + js.New(jstpe.ClassType(cls), ctor, List(closure)) + } + } + + /** Boxes a value of the given type before `elimErasedValueType`. + * + * This should be used when sending values to a JavaScript context, which + * is erased/boxed at the IR level, although it is not erased at the + * dotty/JVM level. + * + * @param expr Tree to be boxed if needed. + * @param tpeEnteringElimErasedValueType The type of `expr` as it was + * entering the `elimErasedValueType` phase. + */ + private def box(expr: js.Tree, tpeEnteringElimErasedValueType: Type)( + implicit pos: Position): js.Tree = { + + tpeEnteringElimErasedValueType match { + case tpe if isPrimitiveValueType(tpe) => + makePrimitiveBox(expr, tpe) + + /*case tpe: ErasedValueType => + val boxedClass = tpe.valueClazz + val ctor = boxedClass.primaryConstructor + genNew(boxedClass, ctor, List(expr))*/ + + case _ => + expr + } + } + + /** Unboxes a value typed as Any to the given type before `elimErasedValueType`. + * + * This should be used when receiving values from a JavaScript context, + * which is erased/boxed at the IR level, although it is not erased at the + * dotty/JVM level. + * + * @param expr Tree to be extracted. + * @param tpeEnteringElimErasedValueType The type of `expr` as it was + * entering the `elimErasedValueType` phase. + */ + private def unbox(expr: js.Tree, tpeEnteringElimErasedValueType: Type)( + implicit pos: Position): js.Tree = { + + tpeEnteringElimErasedValueType match { + case tpe if isPrimitiveValueType(tpe) => + makePrimitiveUnbox(expr, tpe) + + /*case tpe: ErasedValueType => + val boxedClass = tpe.valueClazz + val unboxMethod = boxedClass.derivedValueClassUnbox + val content = genApplyMethod( + genAsInstanceOf(expr, tpe), unboxMethod, Nil) + if (unboxMethod.tpe.resultType <:< tpe.erasedUnderlying) + content + else + fromAny(content, tpe.erasedUnderlying)*/ + + case tpe => + genAsInstanceOf(expr, tpe) + } + } + + /** Gen JS code for an asInstanceOf cast (for reference types only) */ + private def genAsInstanceOf(value: js.Tree, to: Type)( + implicit pos: Position): js.Tree = { + + val sym = to.widenDealias.typeSymbol + + if (sym == defn.ObjectClass || isJSType(sym)) { + /* asInstanceOf[Object] always succeeds, and + * asInstanceOf to a raw JS type is completely erased. + */ + value + } else { + js.AsInstanceOf(value, toReferenceType(to)) + } + } + + /** Gen JS code for an isInstanceOf test (for reference types only) */ + private def genIsInstanceOf(tree: Tree, value: js.Tree, to: Type): js.Tree = { + implicit val pos: Position = tree.pos + val sym = to.widenDealias.typeSymbol + + if (sym == defn.ObjectClass) { + js.BinaryOp(js.BinaryOp.!==, value, js.Null()) + } else if (isJSType(sym)) { + if (sym.is(Trait)) { + ctx.error( + s"isInstanceOf[${sym.fullName}] not supported because it is a JS trait", + tree.pos) + js.BooleanLiteral(true) + } else { + js.Unbox(js.JSBinaryOp( + js.JSBinaryOp.instanceof, value, genLoadJSConstructor(sym)), 'Z') + } + } else { + js.IsInstanceOf(value, toReferenceType(to)) + } + } + /** Gen a dynamically linked call to a Scala method. */ private def genApplyMethod(receiver: js.Tree, methodSym: Symbol, arguments: List[js.Tree])( @@ -1066,6 +2087,33 @@ class JSCodeGen()(implicit ctx: Context) { genApplyMethod(genLoadModule(methodSym.owner), methodSym, arguments) } + /** Gen JS code for `new java.lang.String(...)`. + * + * Rewires the instantiation to calling the appropriate overload of + * `newString` in the object `scala.scalajs.runtime.RuntimeString`. + */ + private def genNewString(ctor: Symbol, arguments: List[js.Tree])( + implicit pos: Position): js.Tree = { + js.Apply( + genLoadModule(jsdefn.RuntimeStringModuleClass), + encodeRTStringCtorSym(ctor), arguments)( + jstpe.ClassType(ir.Definitions.StringClass)) + } + + /** Gen a dynamically linked call to a method of java.lang.String. + * + * Forwards the call to the module scala.scalajs.runtime.RuntimeString. + */ + private def genApplyMethodOfString(receiver: js.Tree, + methodSym: Symbol, arguments: List[js.Tree])( + implicit pos: Position): js.Tree = { + js.Apply( + genLoadModule(jsdefn.RuntimeStringModuleClass), + encodeRTStringMethodSym(methodSym), + receiver :: arguments)( + toIRType(patchedResultType(methodSym))) + } + /** Gen a boxing operation (tpe is the primitive type) */ private def makePrimitiveBox(expr: js.Tree, tpe: Type)( implicit pos: Position): js.Tree = { @@ -1146,7 +2194,156 @@ class JSCodeGen()(implicit ctx: Context) { }*/ } - /** Generate loading of a module value + /** Gen actual actual arguments to a JS method call. + * Returns a list of the transformed arguments. + * + * - TODO Repeated arguments (varargs) are expanded + * - Default arguments are omitted or replaced by undefined + * - All arguments are boxed + * + * Repeated arguments that cannot be expanded at compile time (i.e., if a + * Seq is passed to a varargs parameter with the syntax `seq: _*`) will be + * wrapped in a [[js.JSSpread]] node to be expanded at runtime. + */ + private def genActualJSArgs(sym: Symbol, args: List[Tree])( + implicit pos: Position): List[js.Tree] = { + + def paramNamesAndTypes(implicit ctx: Context): List[(Names.TermName, Type)] = + sym.info.paramNamess.flatten.zip(sym.info.paramTypess.flatten) + + val wereRepeated = ctx.atPhase(ctx.elimRepeatedPhase) { implicit ctx => + for ((name, tpe) <- paramNamesAndTypes) + yield (name -> tpe.isRepeatedParam) + }.toMap + + val paramTypes = ctx.atPhase(ctx.elimErasedValueTypePhase) { implicit ctx => + paramNamesAndTypes + }.toMap + + var reversedArgs: List[js.Tree] = Nil + + for ((arg, (paramName, paramType)) <- args.zip(paramNamesAndTypes)) { + val wasRepeated = wereRepeated.getOrElse(paramName, false) + if (wasRepeated) { + reversedArgs = + genJSRepeatedParam(arg) reverse_::: reversedArgs + } else { + val unboxedArg = genExpr(arg) + val boxedArg = unboxedArg match { + case js.UndefinedParam() => + unboxedArg + case _ => + val tpe = paramTypes.getOrElse(paramName, paramType) + box(unboxedArg, tpe) + } + reversedArgs ::= boxedArg + } + } + + /* Remove all consecutive js.UndefinedParam's at the end of the argument + * list. No check is performed whether they may be there, since they will + * only be placed where default arguments can be anyway. + */ + reversedArgs = reversedArgs.dropWhile(_.isInstanceOf[js.UndefinedParam]) + + /* Find remaining js.UndefinedParam and replace by js.Undefined. This can + * happen with named arguments or with multiple argument lists. + */ + reversedArgs = reversedArgs map { + case js.UndefinedParam() => js.Undefined() + case arg => arg + } + + reversedArgs.reverse + } + + /** Gen JS code for a repeated param of a JS method. + * + * In this case `arg` has type `Seq[T]` for some `T`, but the result should + * be an expanded list of the elements in the sequence. So this method + * takes care of the conversion. + * + * It is specialized for the shapes of tree generated by the desugaring + * of repeated params in Scala, so that these are actually expanded at + * compile-time. + * + * Otherwise, it returns a `JSSpread` with the `Seq` converted to a + * `js.Array`. + */ + private def genJSRepeatedParam(arg: Tree): List[js.Tree] = { + tryGenRepeatedParamAsJSArray(arg, handleNil = true).getOrElse { + /* Fall back to calling runtime.genTraversableOnce2jsArray + * to perform the conversion to js.Array, then wrap in a Spread + * operator. + */ + implicit val pos: Position = arg.pos + val jsArrayArg = genModuleApplyMethod( + jsdefn.RuntimePackage_genTraversableOnce2jsArray, + List(genExpr(arg))) + List(js.JSSpread(jsArrayArg)) + } + } + + /** Try and expand an actual argument to a repeated param `(xs: T*)`. + * + * This method recognizes the shapes of tree generated by the desugaring + * of repeated params in Scala, and expands them. + * If `arg` does not have the shape of a generated repeated param, this + * method returns `None`. + */ + private def tryGenRepeatedParamAsJSArray(arg: Tree, + handleNil: Boolean): Option[List[js.Tree]] = { + implicit val pos: Position = arg.pos + + // Given a method `def foo(args: T*)` + arg match { + // foo(arg1, arg2, ..., argN) where N > 0 + case MaybeAsInstanceOf(WrapArray(MaybeAsInstanceOf(array: JavaSeqLiteral))) => + /* Value classes in arrays are already boxed, so no need to use + * the type before erasure. + * TODO Is this true in dotty? + */ + Some(array.elems.map(e => box(genExpr(e), e.tpe))) + + // foo() + case Ident(_) if handleNil && arg.symbol == defn.NilModule => + Some(Nil) + + // foo(argSeq: _*) - cannot be optimized + case _ => + None + } + } + + private object MaybeAsInstanceOf { + def unapply(tree: Tree): Some[Tree] = tree match { + case TypeApply(asInstanceOf_? @ Select(base, _), _) + if asInstanceOf_?.symbol == defn.Any_asInstanceOf => + Some(base) + case _ => + Some(tree) + } + } + + private object WrapArray { + lazy val isWrapArray: Set[Symbol] = { + val names = { + defn.ScalaValueClasses().map(sym => nme.wrapXArray(sym.name)) ++ + Set(nme.wrapRefArray, nme.genericWrapArray) + } + names.map(defn.ScalaPredefModule.requiredMethod(_)).toSet + } + + def unapply(tree: Apply): Option[Tree] = tree match { + case Apply(wrapArray_?, List(wrapped)) if isWrapArray(wrapArray_?.symbol) => + Some(wrapped) + case _ => + None + } + } + + /** Gen JS code for loading a module. + * * Can be given either the module symbol, or its module class symbol. */ private def genLoadModule(sym0: Symbol)(implicit pos: Position): js.Tree = { @@ -1157,17 +2354,40 @@ class JSCodeGen()(implicit ctx: Context) { if (sym1 == defn.StringModule) jsdefn.RuntimeStringModule.moduleClass else sym1 - //val isGlobalScope = sym.tpe.typeSymbol isSubClass JSGlobalScopeClass + if (isJSType(sym)) { + if (isScalaJSDefinedJSClass(sym)) + js.LoadJSModule(jstpe.ClassType(encodeClassFullName(sym))) + else if (sym.derivesFrom(jsdefn.JSGlobalScopeClass)) + genLoadJSGlobal() + else + genLoadNativeJSModule(sym) + } else { + js.LoadModule(jstpe.ClassType(encodeClassFullName(sym))) + } + } + + /** Gen JS code representing the constructor of a JS class. */ + private def genLoadJSConstructor(sym: Symbol)( + implicit pos: Position): js.Tree = { + assert(!isStaticModule(sym) && !sym.is(Trait), + s"genPrimitiveJSClass called with non-class $sym") + js.LoadJSConstructor(jstpe.ClassType(encodeClassFullName(sym))) + } - /*if (isGlobalScope) { - genLoadGlobal() - } else if (isJSNativeClass(sym)) { - genPrimitiveJSModule(sym) - } else {*/ - val cls = jstpe.ClassType(encodeClassFullName(sym)) - if (isRawJSType(sym)) js.LoadJSModule(cls) - else js.LoadModule(cls) - //} + /** Gen JS code representing a native JS module. */ + private def genLoadNativeJSModule(sym: Symbol)( + implicit pos: Position): js.Tree = { + require(sym.is(ModuleClass), + s"genLoadNativeJSModule called with non-module $sym") + fullJSNameOf(sym).split('.').foldLeft(genLoadJSGlobal()) { (memo, chunk) => + js.JSBracketSelect(memo, js.StringLiteral(chunk)) + } + } + + /** Gen JS code to load the JavaScript global scope. */ + private def genLoadJSGlobal()(implicit pos: Position): js.Tree = { + // TODO Change this when upgrading to Scala.js 0.6.8 + js.JSBracketSelect(js.JSEnvInfo(), js.StringLiteral("global")) } /** Generate a Class[_] value (e.g. coming from classOf[T]) */ @@ -1177,4 +2397,11 @@ class JSCodeGen()(implicit ctx: Context) { private def isStaticModule(sym: Symbol): Boolean = sym.is(Module) && sym.isStatic + private def isPrimitiveValueType(tpe: Type): Boolean = { + tpe.widenDealias match { + case JavaArrayType(_) => false + case t => t.typeSymbol.asClass.isPrimitiveValueClass + } + } + } diff --git a/src/dotty/tools/backend/sjs/JSDefinitions.scala b/src/dotty/tools/backend/sjs/JSDefinitions.scala index 0f4415b31..bd0b74031 100644 --- a/src/dotty/tools/backend/sjs/JSDefinitions.scala +++ b/src/dotty/tools/backend/sjs/JSDefinitions.scala @@ -5,6 +5,7 @@ import dotty.tools.dotc.core._ import Types._ import Contexts._ import Symbols._ +import Names._ import StdNames._ import Decorators._ @@ -172,4 +173,27 @@ final class JSDefinitions()(implicit ctx: Context) { lazy val BoxesRunTime_unboxToCharR = defn.BoxesRunTimeModule.requiredMethodRef("unboxToChar") def BoxesRunTime_unboxToChar(implicit ctx: Context): Symbol = BoxesRunTime_unboxToCharR.symbol + /** If `cls` is a class in the scala package, its name, otherwise EmptyTypeName */ + private def scalajsClassName(cls: Symbol)(implicit ctx: Context): TypeName = + if (cls.isClass && cls.owner == ScalaJSJSPackageClass) cls.asClass.name + else EmptyTypeName + + /** Is the given `cls` a class of the form `scala.scalajs.js.prefixN` where + * `N` is a number. + * + * This is similar to `isVarArityClass` in `Definitions.scala`. + */ + private def isScalaJSVarArityClass(cls: Symbol, prefix: Name): Boolean = { + val name = scalajsClassName(cls) + name.startsWith(prefix) && name.drop(prefix.length).forall(_.isDigit) + } + + def isJSFunctionClass(cls: Symbol): Boolean = + isScalaJSVarArityClass(cls, nme.Function) + + private val ThisFunctionName = termName("ThisFunction") + + def isJSThisFunctionClass(cls: Symbol): Boolean = + isScalaJSVarArityClass(cls, ThisFunctionName) + } diff --git a/src/dotty/tools/backend/sjs/JSEncoding.scala b/src/dotty/tools/backend/sjs/JSEncoding.scala index b35be3264..e8ea3258b 100644 --- a/src/dotty/tools/backend/sjs/JSEncoding.scala +++ b/src/dotty/tools/backend/sjs/JSEncoding.scala @@ -19,6 +19,7 @@ import ir.{Trees => js, Types => jstpe} import ScopedVar.withScopedVars import JSDefinitions._ +import JSInterop._ /** Encoding of symbol names for JavaScript * @@ -139,17 +140,32 @@ object JSEncoding { * java.lang.String, which is the `this` parameter. */ def encodeRTStringMethodSym(sym: Symbol)( - implicit ctx: Context, pos: ir.Position): (Symbol, js.Ident) = { - require(sym.is(Flags.Method), "encodeMethodSym called with non-method symbol: " + sym) + implicit ctx: Context, pos: ir.Position): js.Ident = { require(sym.owner == defn.StringClass) require(!sym.isClassConstructor && !sym.is(Flags.Private)) val (encodedName, paramsString) = encodeMethodNameInternal(sym, inRTClass = true) - val methodIdent = js.Ident(encodedName + paramsString, + js.Ident(encodedName + paramsString, Some(sym.unexpandedName.decoded + paramsString)) + } + + /** Encodes a constructor symbol of java.lang.String for use in RuntimeString. + * + * - The name is rerouted to `newString` + * - The result type is set to `java.lang.String` + */ + def encodeRTStringCtorSym(sym: Symbol)( + implicit ctx: Context, pos: ir.Position): js.Ident = { + require(sym.owner == defn.StringClass) + require(sym.isClassConstructor && !sym.is(Flags.Private)) - (jsdefn.RuntimeStringModuleClass, methodIdent) + val paramTypeNames = sym.info.firstParamTypes.map(internalName(_)) + val paramAndResultTypeNames = paramTypeNames :+ ir.Definitions.StringClass + val paramsString = makeParamsString(paramAndResultTypeNames) + + js.Ident("newString" + paramsString, + Some(sym.unexpandedName.decoded + paramsString)) } private def encodeMethodNameInternal(sym: Symbol, @@ -197,7 +213,7 @@ object JSEncoding { def encodeClassType(sym: Symbol)(implicit ctx: Context): jstpe.Type = { if (sym == defn.ObjectClass) jstpe.AnyType - else if (isRawJSType(sym)) jstpe.AnyType + else if (isJSType(sym)) jstpe.AnyType else { assert(sym != defn.ArrayClass, "encodeClassType() cannot be called with ArrayClass") @@ -210,20 +226,17 @@ object JSEncoding { js.Ident(encodeClassFullName(sym), Some(sym.fullName.toString)) } - def encodeClassFullName(sym: Symbol)(implicit ctx: Context): String = - ir.Definitions.encodeClassName(sym.fullName.toString) + def encodeClassFullName(sym: Symbol)(implicit ctx: Context): String = { + if (sym == defn.NothingClass) ir.Definitions.RuntimeNothingClass + else if (sym == defn.NullClass) ir.Definitions.RuntimeNullClass + else ir.Definitions.encodeClassName(sym.fullName.toString) + } private def encodeMemberNameInternal(sym: Symbol)( implicit ctx: Context): String = { - sym.name.toString.replace("_", "$und") + sym.name.toString.replace("_", "$und").replace("~", "$tilde") } - def isRawJSType(sym: Symbol)(implicit ctx: Context): Boolean = - sym.hasAnnotation(jsdefn.RawJSTypeAnnot) - - def isScalaJSDefinedJSClass(sym: Symbol)(implicit ctx: Context): Boolean = - isRawJSType(sym) && !sym.hasAnnotation(jsdefn.JSNativeAnnot) - def toIRType(tp: Type)(implicit ctx: Context): jstpe.Type = { val refType = toReferenceTypeInternal(tp) refType._1 match { @@ -243,7 +256,7 @@ object JSEncoding { else jstpe.IntType } else { - if (sym == defn.ObjectClass || isRawJSType(sym)) + if (sym == defn.ObjectClass || isJSType(sym)) jstpe.AnyType else if (sym == defn.NothingClass) jstpe.NothingType diff --git a/src/dotty/tools/backend/sjs/JSInterop.scala b/src/dotty/tools/backend/sjs/JSInterop.scala new file mode 100644 index 000000000..6d66c3206 --- /dev/null +++ b/src/dotty/tools/backend/sjs/JSInterop.scala @@ -0,0 +1,110 @@ +package dotty.tools.backend.sjs + +import dotty.tools.dotc.core._ +import Contexts._ +import Flags._ +import Symbols._ +import NameOps._ +import StdNames._ + +import JSDefinitions._ + +/** Management of the interoperability with JavaScript. */ +object JSInterop { + + /** Is this symbol a JavaScript type? */ + def isJSType(sym: Symbol)(implicit ctx: Context): Boolean = { + //sym.hasAnnotation(jsdefn.RawJSTypeAnnot) + ctx.atPhase(ctx.erasurePhase) { implicit ctx => + sym.derivesFrom(jsdefn.JSAnyClass) + } + } + + /** Is this symbol a Scala.js-defined JS class, i.e., a non-native JS class? */ + def isScalaJSDefinedJSClass(sym: Symbol)(implicit ctx: Context): Boolean = + isJSType(sym) && !sym.hasAnnotation(jsdefn.JSNativeAnnot) + + /** Should this symbol be translated into a JS getter? + * + * This is true for any parameterless method, i.e., defined without `()`. + * Unlike `SymDenotations.isGetter`, it applies to user-defined methods as + * much as *accessor* methods created for `val`s and `var`s. + */ + def isJSGetter(sym: Symbol)(implicit ctx: Context): Boolean = { + sym.info.firstParamTypes.isEmpty && ctx.atPhase(ctx.erasurePhase) { implicit ctx => + sym.info.isParameterless + } + } + + /** Should this symbol be translated into a JS setter? + * + * This is true for any method whose name ends in `_=`. + * Unlike `SymDenotations.isGetter`, it applies to user-defined methods as + * much as *accessor* methods created for `var`s. + */ + def isJSSetter(sym: Symbol)(implicit ctx: Context): Boolean = + sym.name.isSetterName && sym.is(Method) + + /** Should this symbol be translated into a JS bracket access? + * + * This is true for methods annotated with `@JSBracketAccess`. + */ + def isJSBracketAccess(sym: Symbol)(implicit ctx: Context): Boolean = + sym.hasAnnotation(jsdefn.JSBracketAccessAnnot) + + /** Should this symbol be translated into a JS bracket call? + * + * This is true for methods annotated with `@JSBracketCall`. + */ + def isJSBracketCall(sym: Symbol)(implicit ctx: Context): Boolean = + sym.hasAnnotation(jsdefn.JSBracketCallAnnot) + + /** Is this symbol a default param accessor for a JS method? + * + * For default param accessors of *constructors*, we need to test whether + * the companion *class* of the owner is a JS type; not whether the owner + * is a JS type. + */ + def isJSDefaultParam(sym: Symbol)(implicit ctx: Context): Boolean = { + sym.name.isDefaultGetterName && { + val owner = sym.owner + if (owner.is(ModuleClass) && + sym.name.asTermName.defaultGetterToMethod == nme.CONSTRUCTOR) { + isJSType(owner.linkedClass) + } else { + isJSType(owner) + } + } + } + + /** Gets the unqualified JS name of a symbol. + * + * If it is not explicitly specified with an `@JSName` annotation, the + * JS name is inferred from the Scala name. + */ + def jsNameOf(sym: Symbol)(implicit ctx: Context): String = { + sym.getAnnotation(jsdefn.JSNameAnnot).flatMap(_.argumentConstant(0)).fold { + val base = sym.name.unexpandedName.decode.toString.stripSuffix("_=") + if (sym.is(ModuleClass)) base.stripSuffix("$") + else if (!sym.is(Method)) base.stripSuffix(" ") + else base + } { constant => + constant.stringValue + } + } + + /** Gets the fully qualified JS name of a static class of module Symbol. + * + * This is the JS name of the symbol qualified by the fully qualified JS + * name of its original owner if the latter is a native JS object. + */ + def fullJSNameOf(sym: Symbol)(implicit ctx: Context): String = { + assert(sym.isClass, s"fullJSNameOf called for non-class symbol $sym") + sym.getAnnotation(jsdefn.JSFullNameAnnot).flatMap(_.argumentConstant(0)).fold { + jsNameOf(sym) + } { constant => + constant.stringValue + } + } + +} diff --git a/src/dotty/tools/backend/sjs/JSPrimitives.scala b/src/dotty/tools/backend/sjs/JSPrimitives.scala index 47d705fe8..52b5dc4b9 100644 --- a/src/dotty/tools/backend/sjs/JSPrimitives.scala +++ b/src/dotty/tools/backend/sjs/JSPrimitives.scala @@ -37,10 +37,13 @@ object JSPrimitives { final val ENV_INFO = 316 // runtime.environmentInfo final val LINKING_INFO = 317 // runtime.linkingInfo + final val THROW = 318 // <special-ops>.throw + } class JSPrimitives(ctx: Context) extends DottyPrimitives(ctx) { import JSPrimitives._ + import scala.tools.nsc.backend.ScalaPrimitives._ private lazy val jsPrimitives: Map[Symbol, Int] = initJSPrimitives(ctx) @@ -77,6 +80,18 @@ class JSPrimitives(ctx: Context) extends DottyPrimitives(ctx) { val jsdefn = JSDefinitions.jsdefn + // For some reason, the JVM primitive set does not register those + addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newBooleanArray")), NEW_ZARRAY) + addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newByteArray")), NEW_BARRAY) + addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newShortArray")), NEW_SARRAY) + addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newCharArray")), NEW_CARRAY) + addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newIntArray")), NEW_IARRAY) + addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newLongArray")), NEW_LARRAY) + addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newFloatArray")), NEW_FARRAY) + addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newDoubleArray")), NEW_DARRAY) + addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newRefArray")), NEW_OARRAY) + addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newUnitArray")), NEW_OARRAY) + addPrimitive(defn.Any_getClass, GETCLASS) for (i <- 0 to 22) @@ -107,6 +122,8 @@ class JSPrimitives(ctx: Context) extends DottyPrimitives(ctx) { //addPrimitive(jsdefn.Runtime_environmentInfo, ENV_INFO) //addPrimitive(jsdefn.Runtime_linkingInfo, LINKING_INFO) + addPrimitive(defn.throwMethod, THROW) + primitives.toMap } diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index d9f1a3dca..fe16243bb 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -104,7 +104,11 @@ class Compiler { def rootContext(implicit ctx: Context): Context = { ctx.initialize()(ctx) val actualPhases = if (ctx.settings.scalajs.value) { - phases + // Remove phases that Scala.js does not want + phases.mapConserve(_.filter { + case _: FunctionalInterfaces => false + case _ => true + }).filter(_.nonEmpty) } else { // Remove Scala.js-related phases phases.mapConserve(_.filter { diff --git a/src/dotty/tools/dotc/config/JavaPlatform.scala b/src/dotty/tools/dotc/config/JavaPlatform.scala index 432a9a0b7..433b5b3f0 100644 --- a/src/dotty/tools/dotc/config/JavaPlatform.scala +++ b/src/dotty/tools/dotc/config/JavaPlatform.scala @@ -7,6 +7,7 @@ import ClassPath.{ JavaContext, DefaultJavaContext } import core._ import Symbols._, Types._, Contexts._, Denotations._, SymDenotations._, StdNames._, Names._ import Flags._, Scopes._, Decorators._, NameOps._, util.Positions._ +import transform.ExplicitOuter, transform.SymUtils._ class JavaPlatform extends Platform { @@ -38,6 +39,14 @@ class JavaPlatform extends Platform { def rootLoader(root: TermSymbol)(implicit ctx: Context): SymbolLoader = new ctx.base.loaders.PackageLoader(root, classPath) + /** Is the SAMType `cls` also a SAM under the rules of the JVM? */ + def isSam(cls: ClassSymbol)(implicit ctx: Context): Boolean = + cls.is(NoInitsTrait) && + cls.superClass == defn.ObjectClass && + cls.directlyInheritedTraits.forall(_.is(NoInits)) && + !ExplicitOuter.needsOuterIfReferenced(cls) && + cls.typeRef.fields.isEmpty // Superaccessors already show up as abstract methods here, so no test necessary + /** We could get away with excluding BoxedBooleanClass for the * purpose of equality testing since it need not compare equal * to anything but other booleans, but it should be present in diff --git a/src/dotty/tools/dotc/config/Platform.scala b/src/dotty/tools/dotc/config/Platform.scala index 972892d12..062d9002d 100644 --- a/src/dotty/tools/dotc/config/Platform.scala +++ b/src/dotty/tools/dotc/config/Platform.scala @@ -27,6 +27,9 @@ abstract class Platform { /** Any platform-specific phases. */ //def platformPhases: List[SubComponent] + /** Is the SAMType `cls` also a SAM under the rules of the platform? */ + def isSam(cls: ClassSymbol)(implicit ctx: Context): Boolean + /** The various ways a boxed primitive might materialize at runtime. */ def isMaybeBoxed(sym: ClassSymbol)(implicit ctx: Context): Boolean diff --git a/src/dotty/tools/dotc/config/SJSPlatform.scala b/src/dotty/tools/dotc/config/SJSPlatform.scala index fec9c25a1..3ec8049ae 100644 --- a/src/dotty/tools/dotc/config/SJSPlatform.scala +++ b/src/dotty/tools/dotc/config/SJSPlatform.scala @@ -2,6 +2,7 @@ package dotty.tools.dotc.config import dotty.tools.dotc.core._ import Contexts._ +import Symbols._ import dotty.tools.backend.sjs.JSDefinitions @@ -10,4 +11,8 @@ class SJSPlatform()(implicit ctx: Context) extends JavaPlatform { /** Scala.js-specific definitions. */ val jsDefinitions: JSDefinitions = new JSDefinitions() + /** Is the SAMType `cls` also a SAM under the rules of the Scala.js back-end? */ + override def isSam(cls: ClassSymbol)(implicit ctx: Context): Boolean = + defn.isFunctionClass(cls) || jsDefinitions.isJSFunctionClass(cls) + } diff --git a/src/dotty/tools/dotc/core/Phases.scala b/src/dotty/tools/dotc/core/Phases.scala index b60f437d5..ce87506ae 100644 --- a/src/dotty/tools/dotc/core/Phases.scala +++ b/src/dotty/tools/dotc/core/Phases.scala @@ -233,8 +233,10 @@ object Phases { private val picklerCache = new PhaseCache(classOf[Pickler]) private val refChecksCache = new PhaseCache(classOf[RefChecks]) + private val elimRepeatedCache = new PhaseCache(classOf[ElimRepeated]) private val extensionMethodsCache = new PhaseCache(classOf[ExtensionMethods]) private val erasureCache = new PhaseCache(classOf[Erasure]) + private val elimErasedValueTypeCache = new PhaseCache(classOf[ElimErasedValueType]) private val patmatCache = new PhaseCache(classOf[PatternMatcher]) private val lambdaLiftCache = new PhaseCache(classOf[LambdaLift]) private val flattenCache = new PhaseCache(classOf[Flatten]) @@ -245,8 +247,10 @@ object Phases { def typerPhase = typerCache.phase def picklerPhase = picklerCache.phase def refchecksPhase = refChecksCache.phase + def elimRepeatedPhase = elimRepeatedCache.phase def extensionMethodsPhase = extensionMethodsCache.phase def erasurePhase = erasureCache.phase + def elimErasedValueTypePhase = elimErasedValueTypeCache.phase def patmatPhase = patmatCache.phase def lambdaLiftPhase = lambdaLiftCache.phase def flattenPhase = flattenCache.phase diff --git a/src/dotty/tools/dotc/transform/ExpandSAMs.scala b/src/dotty/tools/dotc/transform/ExpandSAMs.scala index fd556b572..d9445d046 100644 --- a/src/dotty/tools/dotc/transform/ExpandSAMs.scala +++ b/src/dotty/tools/dotc/transform/ExpandSAMs.scala @@ -25,13 +25,9 @@ class ExpandSAMs extends MiniPhaseTransform { thisTransformer => import ast.tpd._ - /** Is SAMType `cls` also a SAM under the rules of the JVM? */ - def isJvmSam(cls: ClassSymbol)(implicit ctx: Context): Boolean = - cls.is(NoInitsTrait) && - cls.superClass == defn.ObjectClass && - cls.directlyInheritedTraits.forall(_.is(NoInits)) && - !ExplicitOuter.needsOuterIfReferenced(cls) && - cls.typeRef.fields.isEmpty // Superaccessors already show up as abstract methods here, so no test necessary + /** Is the SAMType `cls` also a SAM under the rules of the platform? */ + def isPlatformSam(cls: ClassSymbol)(implicit ctx: Context): Boolean = + ctx.platform.isSam(cls) override def transformBlock(tree: Block)(implicit ctx: Context, info: TransformerInfo): Tree = tree match { case Block(stats @ (fn: DefDef) :: Nil, Closure(_, fnRef, tpt)) if fnRef.symbol == fn.symbol => @@ -39,7 +35,7 @@ class ExpandSAMs extends MiniPhaseTransform { thisTransformer => case NoType => tree // it's a plain function case tpe @ SAMType(_) if tpe.isRef(defn.PartialFunctionClass) => toPartialFunction(tree) - case tpe @ SAMType(_) if isJvmSam(tpe.classSymbol.asClass) => + case tpe @ SAMType(_) if isPlatformSam(tpe.classSymbol.asClass) => tree case tpe => val Seq(samDenot) = tpe.abstractTermMembers.filter(!_.symbol.is(SuperAccessor)) |