diff options
Diffstat (limited to 'src')
41 files changed, 2005 insertions, 281 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/Run.scala b/src/dotty/tools/dotc/Run.scala index 9972e3e64..ee808323a 100644 --- a/src/dotty/tools/dotc/Run.scala +++ b/src/dotty/tools/dotc/Run.scala @@ -8,6 +8,7 @@ import io.PlainFile import util.{SourceFile, NoSource, Stats, SimpleMap} import reporting.Reporter import transform.TreeChecker +import rewrite.Rewrites import java.io.{BufferedWriter, OutputStreamWriter} import scala.reflect.io.VirtualFile import scala.util.control.NonFatal @@ -64,6 +65,7 @@ class Run(comp: Compiler)(implicit ctx: Context) { foreachUnit(printTree) ctx.informTime(s"$phase ", start) } + if (!ctx.reporter.hasErrors) Rewrites.writeBack() } private def printTree(ctx: Context) = { diff --git a/src/dotty/tools/dotc/ast/Desugar.scala b/src/dotty/tools/dotc/ast/Desugar.scala index 8ba155097..7a305a8b8 100644 --- a/src/dotty/tools/dotc/ast/Desugar.scala +++ b/src/dotty/tools/dotc/ast/Desugar.scala @@ -228,7 +228,7 @@ object desugar { val tparam = cpy.TypeDef(tdef)(name = tdef.name.expandedName(ctx.owner)) .withMods(tdef.mods &~ PrivateLocal | ExpandedName) val alias = cpy.TypeDef(tdef)(rhs = refOfDef(tparam), tparams = Nil) - .withFlags(PrivateLocalParamAccessor | Synthetic | tdef.mods.flags & VarianceFlags) + .withMods(tdef.mods & VarianceFlags | PrivateLocalParamAccessor | Synthetic) Thicket(tparam, alias) } else tdef @@ -237,15 +237,15 @@ object desugar { @sharable private val synthetic = Modifiers(Synthetic) private def toDefParam(tparam: TypeDef): TypeDef = - tparam.withFlags(Param) + tparam.withMods(tparam.rawMods & EmptyFlags | Param) private def toDefParam(vparam: ValDef): ValDef = - vparam.withFlags(Param | vparam.rawMods.flags & Implicit) + vparam.withMods(vparam.rawMods & Implicit | Param) /** The expansion of a class definition. See inline comments for what is involved */ def classDef(cdef: TypeDef)(implicit ctx: Context): Tree = { val TypeDef(name, impl @ Template(constr0, parents, self, _)) = cdef val mods = cdef.mods - val accessFlags = (mods.flags & AccessFlags).toCommonFlags + val companionMods = mods.withFlags((mods.flags & AccessFlags).toCommonFlags) val (constr1, defaultGetters) = defDef(constr0, isPrimaryConstructor = true) match { case meth: DefDef => (meth, Nil) @@ -364,7 +364,7 @@ object desugar { moduleDef( ModuleDef( name.toTermName, Template(emptyConstructor, parentTpt :: Nil, EmptyValDef, defs)) - .withFlags(Synthetic | accessFlags)) + .withMods(companionMods | Synthetic)) .withPos(cdef.pos).toList // The companion object definitions, if a companion is needed, Nil otherwise. @@ -421,10 +421,9 @@ object desugar { // implicit wrapper is typechecked in same scope as constructor, so // we can reuse the constructor parameters; no derived params are needed. DefDef(name.toTermName, constrTparams, constrVparamss, classTypeRef, creatorExpr) - .withFlags(Synthetic | Implicit | accessFlags) + .withMods(companionMods | Synthetic | Implicit) .withPos(cdef.pos) :: Nil - val self1 = { val selfType = if (self.tpt.isEmpty) classTypeRef else self.tpt if (self.isEmpty) self @@ -498,18 +497,18 @@ object desugar { /** If `pat` is a variable pattern, * - * val/var p = e + * val/var/lazy val p = e * * Otherwise, in case there is exactly one variable x_1 in pattern - * val/var p = e ==> val/var x_1 = (e: @unchecked) match (case p => (x_1)) + * val/var/lazy val p = e ==> val/var/lazy val x_1 = (e: @unchecked) match (case p => (x_1)) * * in case there are zero or more than one variables in pattern - * val/var p = e ==> private synthetic val t$ = (e: @unchecked) match (case p => (x_1, ..., x_N)) - * val/var x_1 = t$._1 + * val/var/lazy p = e ==> private synthetic [lazy] val t$ = (e: @unchecked) match (case p => (x_1, ..., x_N)) + * val/var/def x_1 = t$._1 * ... - * val/var x_N = t$._N + * val/var/def x_N = t$._N * If the original pattern variable carries a type annotation, so does the corresponding - * ValDef. + * ValDef or DefDef. */ def makePatDef(mods: Modifiers, pat: Tree, rhs: Tree)(implicit ctx: Context): Tree = pat match { case VarPattern(named, tpt) => @@ -533,12 +532,16 @@ object desugar { derivedValDef(named, tpt, matchExpr, mods) case _ => val tmpName = ctx.freshName().toTermName - val patFlags = mods.flags & AccessFlags | Synthetic | (mods.flags & Lazy) - val firstDef = ValDef(tmpName, TypeTree(), matchExpr).withFlags(patFlags) + val patMods = mods & (AccessFlags | Lazy) | Synthetic + val firstDef = + ValDef(tmpName, TypeTree(), matchExpr) + .withPos(pat.pos.union(rhs.pos)).withMods(patMods) def selector(n: Int) = Select(Ident(tmpName), nme.selectorName(n)) val restDefs = for (((named, tpt), n) <- vars.zipWithIndex) - yield derivedValDef(named, tpt, selector(n), mods) + yield + if (mods is Lazy) derivedDefDef(named, tpt, selector(n), mods &~ Lazy) + else derivedValDef(named, tpt, selector(n), mods) flatTree(firstDef :: restDefs) } } @@ -635,6 +638,9 @@ object desugar { private def derivedValDef(named: NameTree, tpt: Tree, rhs: Tree, mods: Modifiers) = ValDef(named.name.asTermName, tpt, rhs).withMods(mods).withPos(named.pos) + private def derivedDefDef(named: NameTree, tpt: Tree, rhs: Tree, mods: Modifiers) = + DefDef(named.name.asTermName, Nil, Nil, tpt, rhs).withMods(mods).withPos(named.pos) + /** Main desugaring method */ def apply(tree: Tree)(implicit ctx: Context): Tree = { diff --git a/src/dotty/tools/dotc/ast/NavigateAST.scala b/src/dotty/tools/dotc/ast/NavigateAST.scala new file mode 100644 index 000000000..782866bad --- /dev/null +++ b/src/dotty/tools/dotc/ast/NavigateAST.scala @@ -0,0 +1,83 @@ +package dotty.tools.dotc +package ast + +import core.Contexts.Context +import core.Decorators._ +import util.Positions._ +import Trees.{MemberDef, DefTree} + +/** Utility functions to go from typed to untyped ASTs */ +object NavigateAST { + + /** The untyped tree corresponding to typed tree `tree` in the compilation + * unit specified by `ctx` + */ + def toUntyped(tree: tpd.Tree)(implicit ctx: Context): untpd.Tree = + untypedPath(tree, exactMatch = true) match { + case (utree: untpd.Tree) :: _ => + utree + case _ => + val loosePath = untypedPath(tree, exactMatch = false) + throw new + Error(i"""no untyped tree for $tree, pos = ${tree.pos}, envelope = ${tree.envelope} + |best matching path =\n$loosePath%\n====\n% + |path positions = ${loosePath.map(_.pos)} + |path envelopes = ${loosePath.map(_.envelope)}""".stripMargin) + } + + /** The reverse path of untyped trees starting with a tree that closest matches + * `tree` and ending in the untyped tree at the root of the compilation unit + * specified by `ctx`. + * @param exactMatch If `true`, the path must start with a node that exactly + * matches `tree`, or `Nil` is returned. + * If `false` the path might start with a node enclosing + * the logical position of `tree`. + * Note: A complication concerns member definitions. ValDefs and DefDefs + * have after desugaring a position that spans just the name of the symbol being + * defined and nothing else. So we look instead for an untyped tree approximating the + * envelope of the definition, and declare success if we find another DefTree. + */ + def untypedPath(tree: tpd.Tree, exactMatch: Boolean = false)(implicit ctx: Context): List[Positioned] = + tree match { + case tree: MemberDef[_] => + untypedPath(tree.envelope) match { + case path @ (last: DefTree[_]) :: _ => path + case path if !exactMatch => path + case _ => Nil + } + case _ => + untypedPath(tree.pos) match { + case (path @ last :: _) if last.pos == tree.pos || !exactMatch => path + case _ => Nil + } + } + + /** The reverse part of the untyped root of the compilation unit of `ctx` to + * position `pos`. + */ + def untypedPath(pos: Position)(implicit ctx: Context): List[Positioned] = + pathTo(pos, ctx.compilationUnit.untpdTree) + + + /** The reverse path from node `from` to the node that closest encloses position `pos`, + * or `Nil` if no such path exists. If a non-empty path is returned it starts with + * the node closest enclosing `pos` and ends with `from`. + */ + def pathTo(pos: Position, from: Positioned)(implicit ctx: Context): List[Positioned] = { + def childPath(it: Iterator[Any], path: List[Positioned]): List[Positioned] = { + while (it.hasNext) { + val path1 = it.next match { + case p: Positioned => singlePath(p, path) + case xs: List[_] => childPath(xs.iterator, path) + case _ => path + } + if (path1 ne path) return path1 + } + path + } + def singlePath(p: Positioned, path: List[Positioned]): List[Positioned] = + if (p.envelope contains pos) childPath(p.productIterator, p :: path) + else path + singlePath(from, Nil) + } +}
\ No newline at end of file diff --git a/src/dotty/tools/dotc/ast/Positioned.scala b/src/dotty/tools/dotc/ast/Positioned.scala index e0bd6c75a..e7f5de591 100644 --- a/src/dotty/tools/dotc/ast/Positioned.scala +++ b/src/dotty/tools/dotc/ast/Positioned.scala @@ -54,21 +54,55 @@ abstract class Positioned extends DotClass with Product { */ private[dotc] def setPosUnchecked(pos: Position) = curPos = pos - /** If any children of this node do not have positions, set them to the given position, + /** If any children of this node do not have positions, + * fit their positions between the positions of the known subtrees * and transitively visit their children. + * The method is likely time-critical because it is invoked on any node + * we create, so we want to avoid object allocations in the common case. + * The method is naturally expressed as two mutually (tail-)recursive + * functions, one which computes the next element to consider or terminates if there + * is none and the other which propagates the position information to that element. + * But since mutual tail recursion is not supported in Scala, we express it instead + * as a while loop with a termination by return in the middle. */ private def setChildPositions(pos: Position): Unit = { - def deepSetPos(x: Any): Unit = x match { - case p: Positioned => - if (!p.pos.exists) p.setPos(pos) - case xs: List[_] => - xs foreach deepSetPos - case _ => + var n = productArity // subnodes are analyzed right to left + var elems: List[Any] = Nil // children in lists still to be considered, from right to left + var end = pos.end // the last defined offset, fill in positions up to this offset + var outstanding: List[Positioned] = Nil // nodes that need their positions filled once a start position + // is known, from left to right. + def fillIn(ps: List[Positioned], start: Int, end: Int): Unit = ps match { + case p :: ps1 => + p.setPos(Position(start, end)) + fillIn(ps1, end, end) + case nil => } - var n = productArity - while (n > 0) { - n -= 1 - deepSetPos(productElement(n)) + while (true) { + var nextChild: Any = null // the next child to be considered + if (elems.nonEmpty) { + nextChild = elems.head + elems = elems.tail + } + else if (n > 0) { + n = n - 1 + nextChild = productElement(n) + } + else { + fillIn(outstanding, pos.start, end) + return + } + nextChild match { + case p: Positioned => + if (p.pos.exists) { + fillIn(outstanding, p.pos.end, end) + outstanding = Nil + end = p.pos.start + } + else outstanding = p :: outstanding + case xs: List[_] => + elems = elems ::: xs.reverse + case _ => + } } } @@ -114,26 +148,4 @@ abstract class Positioned extends DotClass with Product { found } } - - /** The path from this node to `that` node, represented - * as a list starting with `this`, ending with`that` where - * every node is a child of its predecessor. - * Nil if no such path exists. - */ - def pathTo(that: Positioned): List[Positioned] = { - def childPath(it: Iterator[Any]): List[Positioned] = - if (it.hasNext) { - val cpath = it.next match { - case x: Positioned => x.pathTo(that) - case xs: List[_] => childPath(xs.iterator) - case _ => Nil - } - if (cpath.nonEmpty) cpath else childPath(it) - } else Nil - if (this eq that) this :: Nil - else if (this.envelope contains that.pos) { - val cpath = childPath(productIterator) - if (cpath.nonEmpty) this :: cpath else Nil - } else Nil - } } diff --git a/src/dotty/tools/dotc/ast/TreeTypeMap.scala b/src/dotty/tools/dotc/ast/TreeTypeMap.scala index d714a3d21..a35fe2e8f 100644 --- a/src/dotty/tools/dotc/ast/TreeTypeMap.scala +++ b/src/dotty/tools/dotc/ast/TreeTypeMap.scala @@ -82,7 +82,8 @@ final class TreeTypeMap( constr = tmap.transformSub(constr), parents = parents mapconserve transform, self = tmap.transformSub(self), - body = impl.body mapconserve tmap.transform + body = impl.body mapconserve + (tmap.transform(_)(ctx.withOwner(mapOwner(impl.symbol.owner)))) ).withType(tmap.mapType(impl.tpe)) case tree1 => tree1.withType(mapType(tree1.tpe)) match { diff --git a/src/dotty/tools/dotc/ast/Trees.scala b/src/dotty/tools/dotc/ast/Trees.scala index 54ace3be4..fbb93d141 100644 --- a/src/dotty/tools/dotc/ast/Trees.scala +++ b/src/dotty/tools/dotc/ast/Trees.scala @@ -50,13 +50,17 @@ object Trees { def toTypeFlags: Modifiers[T] = withFlags(flags.toTypeFlags) def toTermFlags: Modifiers[T] = withFlags(flags.toTermFlags) - private def withFlags(flags: FlagSet) = + def withFlags(flags: FlagSet) = if (this.flags == flags) this else copy(flags = flags) + def withAddedAnnotation[U >: Untyped <: T](annot: Tree[U]): Modifiers[U] = + if (annotations.exists(_ eq annot)) this + else withAnnotations(annotations :+ annot) + def withAnnotations[U >: Untyped <: T](annots: List[Tree[U]]): Modifiers[U] = - if (annots.isEmpty) this - else copy(annotations = annotations ++ annots) + if (annots eq annotations) this + else copy(annotations = annots) def withPrivateWithin(pw: TypeName) = if (pw.isEmpty) this 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/config/ScalaSettings.scala b/src/dotty/tools/dotc/config/ScalaSettings.scala index aa264329c..07a23fdb6 100644 --- a/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -1,6 +1,8 @@ -package dotty.tools.dotc.config +package dotty.tools.dotc +package config import PathResolver.Defaults +import rewrite.Rewrites class ScalaSettings extends Settings.SettingGroup { @@ -48,6 +50,7 @@ class ScalaSettings extends Settings.SettingGroup { val d = StringSetting("-d", "directory|jar", "destination for generated classfiles.", ".") val nospecialization = BooleanSetting("-no-specialization", "Ignore @specialize annotations.") val language = MultiStringSetting("-language", "feature", "Enable one or more language features.") + val rewrite = OptionSetting[Rewrites]("-rewrite", "When used in conjunction with -language:Scala2 rewrites sources to migrate to new syntax") /** -X "Advanced" settings */ @@ -105,10 +108,10 @@ class ScalaSettings extends Settings.SettingGroup { val YcheckMods = BooleanSetting("-Ycheck-mods", "Check that symbols and their defining trees have modifiers in sync") val YcheckTypedTrees = BooleanSetting("-YcheckTypedTrees", "Check all constructured typed trees for type correctness") val Yshow = PhasesSetting("-Yshow", "(Requires -Xshow-class or -Xshow-object) Show after") - val Xcloselim = BooleanSetting("-Yclosure-elim", "Perform closure elimination.") + val Ycloselim = BooleanSetting("-Yclosure-elim", "Perform closure elimination.") val Ycompacttrees = BooleanSetting("-Ycompact-trees", "Use compact tree printer when displaying trees.") val noCompletion = BooleanSetting("-Yno-completion", "Disable tab-completion in the REPL.") - val Xdce = BooleanSetting("-Ydead-code", "Perform dead code elimination.") + val Ydce = BooleanSetting("-Ydead-code", "Perform dead code elimination.") val debug = BooleanSetting("-Ydebug", "Increase the quantity of debugging output.") val debugNames = BooleanSetting("-YdebugNames", "Show name-space indicators when printing names") val debugTrace = BooleanSetting("-Ydebug-trace", "Trace core operations") @@ -119,7 +122,7 @@ class ScalaSettings extends Settings.SettingGroup { val inline = BooleanSetting("-Yinline", "Perform inlining when possible.") val inlineHandlers = BooleanSetting("-Yinline-handlers", "Perform exception handler inlining when possible.") val YinlinerWarnings = BooleanSetting("-Yinline-warnings", "Emit inlining warnings. (Normally surpressed due to high volume)") - val Xlinearizer = ChoiceSetting("-Ylinearizer", "which", "Linearizer to use", List("normal", "dfs", "rpo", "dump"), "rpo") + val Ylinearizer = ChoiceSetting("-Ylinearizer", "which", "Linearizer to use", List("normal", "dfs", "rpo", "dump"), "rpo") val log = PhasesSetting("-Ylog", "Log operations during") val Ylogcp = BooleanSetting("-Ylog-classpath", "Output information about what classpath is being applied.") val Ynogenericsig = BooleanSetting("-Yno-generic-signatures", "Suppress generation of generic signatures for Java.") @@ -127,9 +130,9 @@ class ScalaSettings extends Settings.SettingGroup { val nopredef = BooleanSetting("-Yno-predef", "Compile without importing Predef.") val noAdaptedArgs = BooleanSetting("-Yno-adapted-args", "Do not adapt an argument list (either by inserting () or creating a tuple) to match the receiver.") val selfInAnnots = BooleanSetting("-Yself-in-annots", "Include a \"self\" identifier inside of annotations.") - val Xshowtrees = BooleanSetting("-Yshow-trees", "(Requires -Xprint:) Print detailed ASTs in formatted form.") - val XshowtreesCompact = BooleanSetting("-Yshow-trees-compact", "(Requires -Xprint:) Print detailed ASTs in compact form.") - val XshowtreesStringified = BooleanSetting("-Yshow-trees-stringified", "(Requires -Xprint:) Print stringifications along with detailed ASTs.") + val Yshowtrees = BooleanSetting("-Yshow-trees", "(Requires -Xprint:) Print detailed ASTs in formatted form.") + val YshowtreesCompact = BooleanSetting("-Yshow-trees-compact", "(Requires -Xprint:) Print detailed ASTs in compact form.") + val YshowtreesStringified = BooleanSetting("-Yshow-trees-stringified", "(Requires -Xprint:) Print stringifications along with detailed ASTs.") val Yshowsyms = BooleanSetting("-Yshow-syms", "Print the AST symbol hierarchy after each phase.") val Yshowsymkinds = BooleanSetting("-Yshow-symkinds", "Print abbreviated symbol kinds next to symbol names.") val Yskip = PhasesSetting("-Yskip", "Skip") diff --git a/src/dotty/tools/dotc/config/Settings.scala b/src/dotty/tools/dotc/config/Settings.scala index 73bb056aa..eddeb83ab 100644 --- a/src/dotty/tools/dotc/config/Settings.scala +++ b/src/dotty/tools/dotc/config/Settings.scala @@ -19,6 +19,7 @@ object Settings { val StringTag = ClassTag(classOf[String]) val ListTag = ClassTag(classOf[List[_]]) val VersionTag = ClassTag(classOf[ScalaVersion]) + val OptionTag = ClassTag(classOf[Option[_]]) class SettingsState(initialValues: Seq[Any]) { private var values = ArrayBuffer(initialValues: _*) @@ -55,7 +56,8 @@ object Settings { choices: Seq[T] = Nil, prefix: String = "", aliases: List[String] = Nil, - depends: List[(Setting[_], Any)] = Nil)(private[Settings] val idx: Int) { + depends: List[(Setting[_], Any)] = Nil, + propertyClass: Option[Class[_]] = None)(private[Settings] val idx: Int) { def withAbbreviation(abbrv: String): Setting[T] = copy(aliases = aliases :+ abbrv)(idx) @@ -112,6 +114,8 @@ object Settings { def doSet(argRest: String) = ((implicitly[ClassTag[T]], args): @unchecked) match { case (BooleanTag, _) => update(true, args) + case (OptionTag, _) => + update(Some(propertyClass.get.newInstance), args) case (ListTag, _) => if (argRest.isEmpty) missingArg else update((argRest split ",").toList, args) @@ -255,5 +259,8 @@ object Settings { def VersionSetting(name: String, descr: String, default: ScalaVersion = NoScalaVersion): Setting[ScalaVersion] = publish(Setting(name, descr, default)) + + def OptionSetting[T: ClassTag](name: String, descr: String): Setting[Option[T]] = + publish(Setting(name, descr, None, propertyClass = Some(implicitly[ClassTag[T]].runtimeClass))) } } diff --git a/src/dotty/tools/dotc/core/Flags.scala b/src/dotty/tools/dotc/core/Flags.scala index ebf4c637a..f866621f2 100644 --- a/src/dotty/tools/dotc/core/Flags.scala +++ b/src/dotty/tools/dotc/core/Flags.scala @@ -448,7 +448,7 @@ object Flags { final val FromStartFlags = AccessFlags | Module | Package | Deferred | Final | MethodOrHKCommon | Param | ParamAccessor | Scala2ExistentialCommon | InSuperCall | Touched | JavaStatic | CovariantOrOuter | ContravariantOrLabel | ExpandedName | AccessorOrSealed | - CaseAccessorOrBaseTypeArg | Fresh | Frozen | Erroneous | ImplicitCommon | Permanent | + CaseAccessorOrBaseTypeArg | Fresh | Frozen | Erroneous | ImplicitCommon | Permanent | Synthetic | LazyOrTrait | SuperAccessorOrScala2x | SelfNameOrImplClass assert(FromStartFlags.isTermFlags && FromStartFlags.isTypeFlags) diff --git a/src/dotty/tools/dotc/core/Hashable.scala b/src/dotty/tools/dotc/core/Hashable.scala index 6a64f8655..12a408fbd 100644 --- a/src/dotty/tools/dotc/core/Hashable.scala +++ b/src/dotty/tools/dotc/core/Hashable.scala @@ -92,7 +92,9 @@ trait Hashable { protected final def doHash(x1: Int, x2: Int): Int = finishHash(hashing.mix(hashing.mix(hashSeed, x1), x2), 1) - protected final def addDelta(hc: Int, delta: Int) = avoidNotCached(hc + delta) + protected final def addDelta(elemHash: Int, delta: Int) = + if (elemHash == NotCached) NotCached + else avoidNotCached(elemHash + delta) private def avoidNotCached(h: Int) = if (h == NotCached) NotCachedAlt else h } 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/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 734d31858..371be1586 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -546,7 +546,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. hasImport(c) })) def hasOption = ctx.base.settings.language.value exists (s => s == featureName || s == "_") - hasImport || hasOption + hasImport(ctx.withPhase(ctx.typerPhase)) || hasOption } /** Is auto-tupling enabled? */ diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index dd12a0188..3801f1914 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -2424,8 +2424,6 @@ object Types { x => paramBounds mapConserve (_.subst(this, x).bounds), x => resType.subst(this, x)) - // need to override hashCode and equals to be object identity - // because paramNames by itself is not discriminatory enough override def equals(other: Any) = other match { case other: PolyType => other.paramNames == this.paramNames && other.paramBounds == this.paramBounds && other.resType == this.resType @@ -2479,7 +2477,7 @@ object Types { def copyBoundType(bt: BT) = new MethodParamImpl(bt, paramNum) // need to customize hashCode and equals to prevent infinite recursion for dep meth types. - override def computeHash = addDelta(System.identityHashCode(binder), paramNum) + override def computeHash = addDelta(binder.identityHash, paramNum) override def equals(that: Any) = that match { case that: MethodParam => (this.binder eq that.binder) && this.paramNum == that.paramNum diff --git a/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/src/dotty/tools/dotc/core/tasty/TreePickler.scala index bb6c3cd2e..7f8e5cef3 100644 --- a/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -55,11 +55,22 @@ class TreePickler(pickler: TastyPickler) { pickleName(TastyName.Signed(nameIndex(name), params.map(fullNameIndex), fullNameIndex(result))) } - private def pickleName(sym: Symbol)(implicit ctx: Context): Unit = - if (sym is Flags.ExpandedName) - pickleName(TastyName.Expanded( - nameIndex(sym.name.expandedPrefix), nameIndex(sym.name.unexpandedName))) - else pickleName(sym.name) + private def pickleName(sym: Symbol)(implicit ctx: Context): Unit = { + def encodeSuper(name: Name): TastyName.NameRef = + if (sym is Flags.SuperAccessor) { + val SuperAccessorName(n) = name + nameIndex(TastyName.SuperAccessor(nameIndex(n))) + } + else nameIndex(name) + val nameRef = + if (sym is Flags.ExpandedName) + nameIndex( + TastyName.Expanded( + nameIndex(sym.name.expandedPrefix), + encodeSuper(sym.name.unexpandedName))) + else encodeSuper(sym.name) + writeNat(nameRef.index) + } private def pickleSymRef(sym: Symbol)(implicit ctx: Context) = symRefs.get(sym) match { case Some(label) => diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 16caac02e..016aa41de 100644 --- a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -78,7 +78,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { case Shadowed(original) => toTermName(original).shadowedName case Expanded(prefix, original) => toTermName(original).expandedName(toTermName(prefix)) case ModuleClass(original) => toTermName(original).moduleClassName.toTermName - case SuperAccessor(accessed) => ??? + case SuperAccessor(accessed) => toTermName(accessed).superName case DefaultGetter(meth, num) => ??? } @@ -378,9 +378,13 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { val rhsIsEmpty = noRhs(end) if (!rhsIsEmpty) skipTree() val (givenFlags, annots, privateWithin) = readModifiers(end) - val expandedFlag = if (rawName.isInstanceOf[TastyName.Expanded]) ExpandedName else EmptyFlags + def nameFlags(tname: TastyName): FlagSet = tname match { + case TastyName.Expanded(_, original) => ExpandedName | nameFlags(tastyName(original)) + case TastyName.SuperAccessor(_) => Flags.SuperAccessor + case _ => EmptyFlags + } pickling.println(i"creating symbol $name at $start with flags $givenFlags") - val flags = normalizeFlags(tag, givenFlags | expandedFlag, name, isAbstractType, rhsIsEmpty) + val flags = normalizeFlags(tag, givenFlags | nameFlags(rawName), name, isAbstractType, rhsIsEmpty) def adjustIfModule(completer: LazyType) = if (flags is Module) ctx.adjustModuleCompleter(completer, name) else completer val sym = diff --git a/src/dotty/tools/dotc/parsing/JavaParsers.scala b/src/dotty/tools/dotc/parsing/JavaParsers.scala index be7822cdc..b4d01a0da 100644 --- a/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -511,7 +511,7 @@ object JavaParsers { atPos(offset) { New(Select(scalaDot(nme.runtime), tpnme.AnnotationDefaultATTR), Nil) } - mods1 = mods1 withAnnotations annot :: Nil + mods1 = mods1 withAddedAnnotation annot skipTo(SEMI) accept(SEMI) unimplementedExpr diff --git a/src/dotty/tools/dotc/parsing/Parsers.scala b/src/dotty/tools/dotc/parsing/Parsers.scala index bb8fbe08b..6ec75a8b2 100644 --- a/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/src/dotty/tools/dotc/parsing/Parsers.scala @@ -21,6 +21,7 @@ import Constants._ import ScriptParsers._ import annotation.switch import util.DotClass +import rewrite.Rewrites.patch object Parsers { @@ -1761,13 +1762,20 @@ object Parsers { * DefSig ::= id [DefTypeParamClause] ParamClauses */ def defDefOrDcl(mods: Modifiers): Tree = atPos(tokenRange) { - def scala2ProcedureSyntax = - testScala2Mode("Procedure syntax no longer supported; `: Unit =' should be inserted here") + def scala2ProcedureSyntax(resultTypeStr: String) = { + val toInsert = + if (in.token == LBRACE) s"$resultTypeStr =" + else ": Unit " // trailing space ensures that `def f()def g()` works. + testScala2Mode(s"Procedure syntax no longer supported; `$toInsert' should be inserted here") && { + patch(source, Position(in.lastOffset), toInsert) + true + } + } if (in.token == THIS) { in.nextToken() val vparamss = paramClauses(nme.CONSTRUCTOR) val rhs = { - if (!(in.token == LBRACE && scala2ProcedureSyntax)) accept(EQUALS) + if (!(in.token == LBRACE && scala2ProcedureSyntax(""))) accept(EQUALS) atPos(in.offset) { constrExpr() } } makeConstructor(Nil, vparamss, rhs).withMods(mods) @@ -1784,7 +1792,7 @@ object Parsers { } else if (!tpt.isEmpty) EmptyTree - else if (scala2ProcedureSyntax) { + else if (scala2ProcedureSyntax(": Unit")) { tpt = scalaUnit if (in.token == LBRACE) expr() else EmptyTree diff --git a/src/dotty/tools/dotc/parsing/Tokens.scala b/src/dotty/tools/dotc/parsing/Tokens.scala index 190226635..b490cd133 100644 --- a/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/src/dotty/tools/dotc/parsing/Tokens.scala @@ -94,8 +94,8 @@ abstract class TokensCommon { /** special symbols */ final val COMMA = 70; enter(COMMA, "','") - final val SEMI = 71; enter(DOT, "'.'") - final val DOT = 72; enter(SEMI, "';'") + final val SEMI = 71; enter(SEMI, "';'") + final val DOT = 72; enter(DOT, "'.'") //final val NEWLINE = 78; enter(NEWLINE, "end of statement", "new line") //final val NEWLINES = 79; enter(NEWLINES, "end of statement", "new lines") diff --git a/src/dotty/tools/dotc/rewrite/Rewrites.scala b/src/dotty/tools/dotc/rewrite/Rewrites.scala new file mode 100644 index 000000000..7ab0e5d59 --- /dev/null +++ b/src/dotty/tools/dotc/rewrite/Rewrites.scala @@ -0,0 +1,92 @@ +package dotty.tools.dotc +package rewrite + +import util.{SourceFile, Positions} +import Positions.Position +import core.Contexts.{Context, FreshContext} +import collection.mutable + +/** Handles rewriting of Scala2 files to Dotty */ +object Rewrites { + private class PatchedFiles extends mutable.HashMap[SourceFile, Patches] + + private case class Patch(pos: Position, replacement: String) { + def delta = replacement.length - (pos.end - pos.start) + } + + private class Patches(source: SourceFile) { + private val pbuf = new mutable.ListBuffer[Patch]() + + def addPatch(pos: Position, replacement: String): Unit = + pbuf += Patch(pos, replacement) + + def apply(cs: Array[Char]): Array[Char] = { + val delta = pbuf.map(_.delta).sum + val patches = pbuf.toList.sortBy(_.pos.start) + if (patches.nonEmpty) + patches reduceLeft {(p1, p2) => + assert(p1.pos.end <= p2.pos.start, s"overlapping patches: $p1 and $p2") + p2 + } + val ds = new Array[Char](cs.length + delta) + def loop(ps: List[Patch], inIdx: Int, outIdx: Int): Unit = { + def copy(upTo: Int): Int = { + val untouched = upTo - inIdx + Array.copy(cs, inIdx, ds, outIdx, untouched) + outIdx + untouched + } + ps match { + case patch @ Patch(pos, replacement) :: ps1 => + val outNew = copy(pos.start) + replacement.copyToArray(ds, outNew) + loop(ps1, pos.end, outNew + replacement.length) + case Nil => + val outNew = copy(cs.length) + assert(outNew == ds.length, s"$outNew != ${ds.length}") + } + } + loop(patches, 0, 0) + ds + } + + def writeBack(): Unit = { + val out = source.file.output + val chars = apply(source.underlying.content) + val bytes = new String(chars).getBytes + out.write(bytes) + out.close() + } + } + + /** If -rewrite is set, record a patch that replaces the range + * given by `pos` in `source` by `replacement` + */ + def patch(source: SourceFile, pos: Position, replacement: String)(implicit ctx: Context): Unit = + for (rewrites <- ctx.settings.rewrite.value) + rewrites.patched + .getOrElseUpdate(source, new Patches(source)) + .addPatch(pos, replacement) + + /** Patch position in `ctx.compilationUnit.source`. */ + def patch(pos: Position, replacement: String)(implicit ctx: Context): Unit = + patch(ctx.compilationUnit.source, pos, replacement) + + /** If -rewrite is set, apply all patches and overwrite patched source files. + */ + def writeBack()(implicit ctx: Context) = + for (rewrites <- ctx.settings.rewrite.value; source <- rewrites.patched.keys) { + ctx.println(s"[patched file ${source.file.path}]") + rewrites.patched(source).writeBack() + } +} + +/** A completely encapsulated class representing rewrite state, used + * as an optional setting. + */ +class Rewrites { + import Rewrites._ + private val patched = new PatchedFiles +} + + + diff --git a/src/dotty/tools/dotc/transform/Constructors.scala b/src/dotty/tools/dotc/transform/Constructors.scala index b6ebd7d90..44638ce48 100644 --- a/src/dotty/tools/dotc/transform/Constructors.scala +++ b/src/dotty/tools/dotc/transform/Constructors.scala @@ -26,7 +26,7 @@ import collection.mutable * - also moves private fields that are accessed only from constructor * into the constructor if possible. */ -class Constructors extends MiniPhaseTransform with SymTransformer { thisTransform => +class Constructors extends MiniPhaseTransform with IdentityDenotTransformer { thisTransform => import tpd._ override def phaseName: String = "constructors" @@ -99,18 +99,6 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor } } - /** Symbols that are owned by either <local dummy> or a class field move into the - * primary constructor. - */ - override def transformSym(sym: SymDenotation)(implicit ctx: Context): SymDenotation = { - def ownerBecomesConstructor(owner: Symbol): Boolean = - (owner.isLocalDummy || owner.isTerm && !owner.is(MethodOrLazy)) && - owner.owner.isClass - if (ownerBecomesConstructor(sym.owner)) - sym.copySymDenotation(owner = sym.owner.enclosingClass.primaryConstructor) - else sym - } - /** @return true if after ExplicitOuter, all references from this tree go via an * outer link, so no parameter accessors need to be rewired to parameters */ diff --git a/src/dotty/tools/dotc/transform/ExpandPrivate.scala b/src/dotty/tools/dotc/transform/ExpandPrivate.scala index f44be3958..a6f203478 100644 --- a/src/dotty/tools/dotc/transform/ExpandPrivate.scala +++ b/src/dotty/tools/dotc/transform/ExpandPrivate.scala @@ -57,8 +57,11 @@ class ExpandPrivate extends MiniPhaseTransform with IdentityDenotTransformer { t * static members of the companion class, we should tighten the condition below. */ private def ensurePrivateAccessible(d: SymDenotation)(implicit ctx: Context) = - if (d.is(PrivateTerm) && d.owner != ctx.owner.enclosingClass) + if (d.is(PrivateTerm) && d.owner != ctx.owner.enclosingClass) { + assert(d.symbol.sourceFile == ctx.source.file, + i"private ${d.symbol.showLocated} in ${d.symbol.sourceFile} accessed from ${ctx.owner.showLocated} in ${ctx.source.file}") d.ensureNotPrivate.installAfter(thisTransform) + } override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo) = { ensurePrivateAccessible(tree.symbol) 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)) diff --git a/src/dotty/tools/dotc/transform/ExplicitOuter.scala b/src/dotty/tools/dotc/transform/ExplicitOuter.scala index 4cf076c45..7ec0739c1 100644 --- a/src/dotty/tools/dotc/transform/ExplicitOuter.scala +++ b/src/dotty/tools/dotc/transform/ExplicitOuter.scala @@ -212,27 +212,34 @@ object ExplicitOuter { /** Tree references an outer class of `cls` which is not a static owner. */ def referencesOuter(cls: Symbol, tree: Tree)(implicit ctx: Context): Boolean = { - def isOuter(sym: Symbol) = + def isOuterSym(sym: Symbol) = !sym.isStaticOwner && cls.isProperlyContainedIn(sym) + def isOuterRef(ref: Type): Boolean = ref match { + case ref: ThisType => + isOuterSym(ref.cls) + case ref: TermRef => + if (ref.prefix ne NoPrefix) + !ref.symbol.isStatic && isOuterRef(ref.prefix) + else if (ref.symbol is Hoistable) + // ref.symbol will be placed in enclosing class scope by LambdaLift, so it might need + // an outer path then. + isOuterSym(ref.symbol.owner.enclosingClass) + else + // ref.symbol will get a proxy in immediately enclosing class. If this properly + // contains the current class, it needs an outer path. + ctx.owner.enclosingClass.owner.enclosingClass.isContainedIn(ref.symbol.owner) + case _ => false + } + def hasOuterPrefix(tp: Type) = tp match { + case TypeRef(prefix, _) => isOuterRef(prefix) + case _ => false + } tree match { - case thisTree @ This(_) => - isOuter(thisTree.symbol) - case id: Ident => - id.tpe match { - case ref @ TermRef(NoPrefix, _) => - if (ref.symbol is Hoistable) - // ref.symbol will be placed in enclosing class scope by LambdaLift, so it might need - // an outer path then. - isOuter(ref.symbol.owner.enclosingClass) - else - // ref.symbol will get a proxy in immediately enclosing class. If this properly - // contains the current class, it needs an outer path. - ctx.owner.enclosingClass.owner.enclosingClass.isContainedIn(ref.symbol.owner) - case _ => false - } + case _: This | _: Ident => isOuterRef(tree.tpe) case nw: New => val newCls = nw.tpe.classSymbol - isOuter(newCls.owner.enclosingClass) || + isOuterSym(newCls.owner.enclosingClass) || + hasOuterPrefix(nw.tpe) || newCls.owner.isTerm && cls.isProperlyContainedIn(newCls) // newCls might get proxies for free variables. If current class is // properly contained in newCls, it needs an outer path to newCls access the @@ -319,7 +326,12 @@ object ExplicitOuter { val outerAccessorCtx = ctx.withPhaseNoLater(ctx.lambdaLiftPhase) // lambdalift mangles local class names, which means we cannot reliably find outer acessors anymore ctx.log(i"outer to $toCls of $tree: ${tree.tpe}, looking for ${outerAccName(treeCls.asClass)(outerAccessorCtx)} in $treeCls") if (treeCls == toCls) tree - else loop(tree.select(outerAccessor(treeCls.asClass)(outerAccessorCtx)).ensureApplied) + else { + val acc = outerAccessor(treeCls.asClass)(outerAccessorCtx) + assert(acc.exists, + i"failure to construct path from ${ctx.owner.ownersIterator.toList}%/% to `this` of ${toCls.showLocated};\n${treeCls.showLocated} does not have an outer accessor") + loop(tree.select(acc).ensureApplied) + } } ctx.log(i"computing outerpath to $toCls from ${ctx.outersIterator.map(_.owner).toList}") loop(start) diff --git a/src/dotty/tools/dotc/transform/FirstTransform.scala b/src/dotty/tools/dotc/transform/FirstTransform.scala index c24c5bbd9..37ae1d94e 100644 --- a/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -44,15 +44,31 @@ class FirstTransform extends MiniPhaseTransform with IdentityDenotTransformer wi this } - def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = tp - - override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = tree match { - case Select(qual, _) if tree.symbol.exists => - assert(qual.tpe derivesFrom tree.symbol.owner, i"non member selection of ${tree.symbol.showLocated} from ${qual.tpe}") - case _: TypeTree => - case _: Import | _: NamedArg | _: TypTree => - assert(false, i"illegal tree: $tree") - case _ => + def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = { + tp match { + //create companions for value classes that are not from currently compiled source file + case tp@ClassInfo(_, cls, _, decls, _) + if (ValueClasses.isDerivedValueClass(cls)) && + !sym.isDefinedInCurrentRun && sym.scalacLinkedClass == NoSymbol => + val newDecls = decls.cloneScope + val (modul, mcMethod, symMethod) = newCompanion(sym.name.toTermName, sym) + modul.entered + mcMethod.entered + newDecls.enter(symMethod) + tp.derivedClassInfo(decls = newDecls) + case _ => tp + } + } + + override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = { + tree match { + case Select(qual, _) if tree.symbol.exists => + assert(qual.tpe derivesFrom tree.symbol.owner, i"non member selection of ${tree.symbol.showLocated} from ${qual.tpe}") + case _: TypeTree => + case _: Import | _: NamedArg | _: TypTree => + assert(false, i"illegal tree: $tree") + case _ => + } } /** Reorder statements so that module classes always come after their companion classes, add missing companion classes */ @@ -81,14 +97,12 @@ class FirstTransform extends MiniPhaseTransform with IdentityDenotTransformer wi case Nil => Nil } - def newCompanion(name: TermName, forClass: Symbol): Thicket = { - val modul = ctx.newCompleteModuleSymbol(ctx.owner, name, Synthetic, Synthetic, - defn.ObjectType :: Nil, Scopes.newScope) - val mc = modul.moduleClass + def registerCompanion(name: TermName, forClass: Symbol): TermSymbol = { + val (modul, mcCompanion, classCompanion) = newCompanion(name, forClass) if (ctx.owner.isClass) modul.enteredAfter(thisTransformer) - ctx.synthesizeCompanionMethod(nme.COMPANION_CLASS_METHOD, forClass, mc).enteredAfter(thisTransformer) - ctx.synthesizeCompanionMethod(nme.COMPANION_MODULE_METHOD, mc, forClass).enteredAfter(thisTransformer) - ModuleDef(modul, Nil) + mcCompanion.enteredAfter(thisTransformer) + classCompanion.enteredAfter(thisTransformer) + modul } def addMissingCompanions(stats: List[Tree]): List[Tree] = stats map { @@ -101,13 +115,23 @@ class FirstTransform extends MiniPhaseTransform with IdentityDenotTransformer wi false } val uniqueName = if (nameClash) objName.avoidClashName else objName - Thicket(stat :: newCompanion(uniqueName, stat.symbol).trees) + Thicket(stat :: ModuleDef(registerCompanion(uniqueName, stat.symbol), Nil).trees) case stat => stat } addMissingCompanions(reorder(stats)) } + private def newCompanion(name: TermName, forClass: Symbol)(implicit ctx: Context) = { + val modul = ctx.newCompleteModuleSymbol(forClass.owner, name, Synthetic, Synthetic, + defn.ObjectType :: Nil, Scopes.newScope, assocFile = forClass.asClass.assocFile) + val mc = modul.moduleClass + + val mcComp = ctx.synthesizeCompanionMethod(nme.COMPANION_CLASS_METHOD, forClass, mc) + val classComp = ctx.synthesizeCompanionMethod(nme.COMPANION_MODULE_METHOD, mc, forClass) + (modul, mcComp, classComp) + } + override def transformDefDef(ddef: DefDef)(implicit ctx: Context, info: TransformerInfo) = { if (ddef.symbol.hasAnnotation(defn.NativeAnnot)) { ddef.symbol.resetFlag(Deferred) diff --git a/src/dotty/tools/dotc/transform/Getters.scala b/src/dotty/tools/dotc/transform/Getters.scala index 882e42d2f..75235d0f5 100644 --- a/src/dotty/tools/dotc/transform/Getters.scala +++ b/src/dotty/tools/dotc/transform/Getters.scala @@ -68,8 +68,8 @@ class Getters extends MiniPhaseTransform with SymTransformer { thisTransform => private val NoGetterNeeded = Method | Param | JavaDefined | JavaStatic override def transformValDef(tree: ValDef)(implicit ctx: Context, info: TransformerInfo): Tree = - if (tree.symbol is Method) DefDef(tree.symbol.asTerm, tree.rhs) else tree + if (tree.symbol is Method) DefDef(tree.symbol.asTerm, tree.rhs).withPos(tree.pos) else tree override def transformAssign(tree: Assign)(implicit ctx: Context, info: TransformerInfo): Tree = - if (tree.lhs.symbol is Method) tree.lhs.becomes(tree.rhs) else tree + if (tree.lhs.symbol is Method) tree.lhs.becomes(tree.rhs).withPos(tree.pos) else tree } diff --git a/src/dotty/tools/dotc/transform/LazyVals.scala b/src/dotty/tools/dotc/transform/LazyVals.scala index 25b9afa68..fc02e68cc 100644 --- a/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/src/dotty/tools/dotc/transform/LazyVals.scala @@ -12,6 +12,8 @@ import Symbols._ import Decorators._ import NameOps._ import StdNames.nme +import rewrite.Rewrites.patch +import util.Positions.Position import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, TreeTransformer, MiniPhaseTransform} import dotty.tools.dotc.ast.Trees._ import dotty.tools.dotc.ast.{untpd, tpd} @@ -65,19 +67,20 @@ class LazyVals extends MiniPhaseTransform with IdentityDenotTransformer with Nee val sym = tree.symbol if (!(sym is Flags.Lazy) || sym.owner.is(Flags.Trait) || (sym.isStatic && sym.is(Flags.Module))) tree else { - val isField = sym.owner.isClass - if (isField) { if (sym.isVolatile || - (sym.is(Flags.Module) && !sym.is(Flags.Synthetic))) - // module class is user-defined. - // Should be threadsafe, to mimic safety guaranteed by global object + (sym.is(Flags.Module)/* || ctx.scala2Mode*/) && + // TODO assume @volatile once LazyVals uses static helper constructs instead of + // ones in the companion object. + !sym.is(Flags.Synthetic)) + // module class is user-defined. + // Should be threadsafe, to mimic safety guaranteed by global object transformMemberDefVolatile(tree) - else if (sym.is(Flags.Module)) { // synthetic module + else if (sym.is(Flags.Module)) // synthetic module transformSyntheticModule(tree) - } - else transformMemberDefNonVolatile(tree) + else + transformMemberDefNonVolatile(tree) } else transformLocalDef(tree) } diff --git a/src/dotty/tools/dotc/transform/Mixin.scala b/src/dotty/tools/dotc/transform/Mixin.scala index 32b268fc7..b0d1e5c5f 100644 --- a/src/dotty/tools/dotc/transform/Mixin.scala +++ b/src/dotty/tools/dotc/transform/Mixin.scala @@ -98,8 +98,12 @@ class Mixin extends MiniPhaseTransform with SymTransformer { thisTransform => override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Erasure]) override def transformSym(sym: SymDenotation)(implicit ctx: Context): SymDenotation = - if (sym.is(Accessor, butNot = Deferred | Lazy) && sym.owner.is(Trait)) - sym.copySymDenotation(initFlags = sym.flags &~ ParamAccessor | Deferred).ensureNotPrivate + if (sym.is(Accessor, butNot = Deferred) && sym.owner.is(Trait)) { + val sym1 = + if (sym is Lazy) sym + else sym.copySymDenotation(initFlags = sym.flags &~ ParamAccessor | Deferred) + sym1.ensureNotPrivate + } else if (sym.isConstructor && sym.owner.is(Trait)) sym.copySymDenotation( name = nme.TRAIT_CONSTRUCTOR, @@ -108,17 +112,19 @@ class Mixin extends MiniPhaseTransform with SymTransformer { thisTransform => sym private def initializer(sym: Symbol)(implicit ctx: Context): TermSymbol = { - val initName = if(!sym.is(Lazy)) InitializerName(sym.name.asTermName) else sym.name.asTermName - sym.owner.info.decl(initName).suchThat(_.is(Lazy) == sym.is(Lazy)).symbol - .orElse( - ctx.newSymbol( - sym.owner, - initName, - Protected | Synthetic | Method, - sym.info, - coord = sym.symbol.coord).enteredAfter(thisTransform)) - .asTerm - } + if (sym is Lazy) sym + else { + val initName = InitializerName(sym.name.asTermName) + sym.owner.info.decl(initName).symbol + .orElse( + ctx.newSymbol( + sym.owner, + initName, + Protected | Synthetic | Method, + sym.info, + coord = sym.symbol.coord).enteredAfter(thisTransform)) + } + }.asTerm override def transformTemplate(impl: Template)(implicit ctx: Context, info: TransformerInfo) = { val cls = impl.symbol.owner.asClass @@ -134,8 +140,8 @@ class Mixin extends MiniPhaseTransform with SymTransformer { thisTransform => val vsym = stat.symbol val isym = initializer(vsym) val rhs = Block( - initBuf.toList.map(_.changeOwner(impl.symbol, isym)), - stat.rhs.changeOwner(vsym, isym).wildcardToDefault) + initBuf.toList.map(_.changeOwnerAfter(impl.symbol, isym, thisTransform)), + stat.rhs.changeOwnerAfter(vsym, isym, thisTransform).wildcardToDefault) initBuf.clear() cpy.DefDef(stat)(rhs = EmptyTree) :: DefDef(isym, rhs) :: Nil case stat: DefDef if stat.symbol.isSetter => diff --git a/src/dotty/tools/dotc/transform/PostTyper.scala b/src/dotty/tools/dotc/transform/PostTyper.scala index ea5c28fb1..01f9f6317 100644 --- a/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/src/dotty/tools/dotc/transform/PostTyper.scala @@ -131,8 +131,13 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisTran annot.derivedAnnotation(transformAnnot(annot.tree)) private def transformMemberDef(tree: MemberDef)(implicit ctx: Context): Unit = { - tree.symbol.transformAnnotations(transformAnnot) - Checking.checkNoPrivateLeaks(tree) + val sym = tree.symbol + sym.transformAnnotations(transformAnnot) + if (!sym.is(SyntheticOrPrivate) && sym.owner.isClass) { + val info1 = Checking.checkNoPrivateLeaks(sym, tree.pos) + if (info1 ne sym.info) + sym.copySymDenotation(info = info1).installAfter(thisTransformer) + } } private def transformSelect(tree: Select, targs: List[Tree])(implicit ctx: Context): Tree = { diff --git a/src/dotty/tools/dotc/transform/TreeChecker.scala b/src/dotty/tools/dotc/transform/TreeChecker.scala index 150a632a1..a260963e9 100644 --- a/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -282,7 +282,10 @@ class TreeChecker extends Phase with SymTransformer { val symbolsNotDefined = cls.classInfo.decls.toSet.filter(isNonMagicalMethod) -- impl.body.map(_.symbol) - constr.symbol - assert(symbolsNotDefined.isEmpty, i" $cls tree does not define methods: $symbolsNotDefined") + assert(symbolsNotDefined.isEmpty, + i" $cls tree does not define methods: ${symbolsNotDefined.toList}%, %\n" + + i"expected: ${cls.classInfo.decls.toSet.filter(isNonMagicalMethod).toList}%, %\n" + + i"defined: ${impl.body.map(_.symbol)}%, %") super.typedClassDef(cdef, cls) } diff --git a/src/dotty/tools/dotc/typer/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala index 0ca121925..9b1f756b7 100644 --- a/src/dotty/tools/dotc/typer/Checking.scala +++ b/src/dotty/tools/dotc/typer/Checking.scala @@ -327,40 +327,53 @@ object Checking { * to a private type or value which is invisible at a point where `M` is still * visible. As an exception, we allow references to type aliases if the underlying * type of the alias is not a leak. So type aliases are transparent as far as - * leak testing is concerned. See 997.scala for tests. + * leak testing is concerned. + * @return The `info` of `sym`, with problematic aliases expanded away. + * See i997.scala for tests, i1130.scala for a case where it matters that we + * transform leaky aliases away. */ - def checkNoPrivateLeaks(tree: MemberDef)(implicit ctx: Context): Unit = { - type Errors = List[(String, Position)] - val sym = tree.symbol - val notPrivate = new TypeAccumulator[Errors] { + def checkNoPrivateLeaks(sym: Symbol, pos: Position)(implicit ctx: Context): Type = { + class NotPrivate extends TypeMap { + type Errors = List[(String, Position)] + var errors: Errors = Nil def accessBoundary(sym: Symbol): Symbol = if (sym.is(Private)) sym.owner else if (sym.privateWithin.exists) sym.privateWithin else if (sym.is(Package)) sym else accessBoundary(sym.owner) - def apply(errors: Errors, tp: Type): Errors = tp match { + def apply(tp: Type): Type = tp match { case tp: NamedType => - val errors1 = + val prevErrors = errors + var tp1 = if (tp.symbol.is(Private) && - !accessBoundary(sym).isContainedIn(tp.symbol.owner)) { - (d"non-private $sym refers to private ${tp.symbol}\n in its type signature ${sym.info}", tree.pos) :: errors - } else foldOver(errors, tp) - if ((errors1 ne errors) && tp.info.isAlias) { + !accessBoundary(sym).isContainedIn(tp.symbol.owner)) { + errors = (d"non-private $sym refers to private ${tp.symbol}\n in its type signature ${sym.info}", + pos) :: errors + tp + } + else mapOver(tp) + if ((errors ne prevErrors) && tp.info.isAlias) { // try to dealias to avoid a leak error - val errors2 = apply(errors, tp.info.bounds.hi) - if (errors2 eq errors) errors2 - else errors1 - } else errors1 + val savedErrors = errors + errors = prevErrors + val tp2 = apply(tp.info.bounds.hi) + if (errors eq prevErrors) tp1 = tp2 + else errors = savedErrors + } + tp1 case tp: ClassInfo => - (apply(errors, tp.prefix) /: tp.parentsWithArgs)(apply) + tp.derivedClassInfo( + prefix = apply(tp.prefix), + classParents = tp.parentsWithArgs.map(p => + apply(p).underlyingClassRef(refinementOK = false).asInstanceOf[TypeRef])) case _ => - foldOver(errors, tp) + mapOver(tp) } } - if (!sym.is(SyntheticOrPrivate) && sym.owner.isClass) { - val errors = notPrivate(Nil, sym.info) - errors.foreach { case (msg, pos) => ctx.errorOrMigrationWarning(msg, pos) } - } + val notPrivate = new NotPrivate + val info = notPrivate(sym.info) + notPrivate.errors.foreach { case (msg, pos) => ctx.errorOrMigrationWarning(msg, pos) } + info } } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 84344dbb1..2069e790b 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -33,6 +33,9 @@ import annotation.tailrec import Implicits._ import util.Stats.{track, record} import config.Printers._ +import rewrite.Rewrites.patch +import NavigateAST._ +import transform.SymUtils._ import language.implicitConversions object Typer { @@ -232,7 +235,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } } val curImport = ctx.importInfo - if (curImport != null && curImport.isRootImport && previous.exists) return previous + if (ctx.owner.is(Package) && curImport != null && curImport.isRootImport && previous.exists) + return previous // no more conflicts possible in this case // would import of kind `prec` be not shadowed by a nested higher-precedence definition? def isPossibleImport(prec: Int) = prevPrec < prec || prevPrec == prec && (prevCtx.scope eq ctx.scope) @@ -983,7 +987,18 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case rhs @ Ident(nme.WILDCARD) => rhs withType tpt1.tpe case rhs => typedExpr(rhs, tpt1.tpe) } - assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym) + val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym) + patchIfLazy(vdef1) + vdef1 + } + + /** Add a @volitile to lazy vals when rewriting from Scala2 */ + private def patchIfLazy(vdef: ValDef)(implicit ctx: Context): Unit = { + val sym = vdef.symbol + if (sym.is(Lazy, butNot = Deferred | Module | Synthetic) && !sym.isVolatile && + ctx.scala2Mode && ctx.settings.rewrite.value.isDefined && + !ctx.isAfterTyper) + patch(Position(toUntyped(vdef).envelope.start), "@volatile ") } def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(implicit ctx: Context) = track("typedDefDef") { @@ -1134,13 +1149,17 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } } - def typedAsFunction(tree: untpd.Tree, pt: Type)(implicit ctx: Context): Tree = { + def typedAsFunction(tree: untpd.PostfixOp, pt: Type)(implicit ctx: Context): Tree = { + val untpd.PostfixOp(qual, nme.WILDCARD) = tree val pt1 = if (defn.isFunctionType(pt)) pt else AnyFunctionProto - var res = typed(tree, pt1) + var res = typed(qual, pt1) if (pt1.eq(AnyFunctionProto) && !defn.isFunctionClass(res.tpe.classSymbol)) { def msg = i"not a function: ${res.tpe}; cannot be followed by `_'" if (ctx.scala2Mode) { + // Under -rewrite, patch `x _` to `(() => x)` ctx.migrationWarning(msg, tree.pos) + patch(Position(tree.pos.start), "(() => ") + patch(Position(qual.pos.end, tree.pos.end), ")") res = typed(untpd.Function(Nil, untpd.TypedSplice(res))) } else ctx.error(msg, tree.pos) @@ -1231,7 +1250,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case tree: untpd.Annotated => typedAnnotated(tree, pt) case tree: untpd.TypedSplice => tree.tree case tree: untpd.UnApply => typedUnApply(tree, pt) - case untpd.PostfixOp(tree, nme.WILDCARD) => typedAsFunction(tree, pt) + case tree @ untpd.PostfixOp(qual, nme.WILDCARD) => typedAsFunction(tree, pt) case untpd.EmptyTree => tpd.EmptyTree case _ => typedUnadapted(desugar(tree), pt) } diff --git a/src/dotty/tools/dotc/typer/VarianceChecker.scala b/src/dotty/tools/dotc/typer/VarianceChecker.scala index b257ee192..274218ee3 100644 --- a/src/dotty/tools/dotc/typer/VarianceChecker.scala +++ b/src/dotty/tools/dotc/typer/VarianceChecker.scala @@ -6,6 +6,8 @@ import core._ import Types._, Contexts._, Flags._, Symbols._, Annotations._, Trees._, NameOps._ import Decorators._ import Variances._ +import util.Positions._ +import rewrite.Rewrites.patch import config.Printers.variances /** Provides `check` method to check that all top-level definitions @@ -108,11 +110,13 @@ class VarianceChecker()(implicit ctx: Context) { } private object Traverser extends TreeTraverser { - def checkVariance(sym: Symbol) = Validator.validateDefinition(sym) match { + def checkVariance(sym: Symbol, pos: Position) = Validator.validateDefinition(sym) match { case Some(VarianceError(tvar, required)) => def msg = i"${varianceString(tvar.flags)} $tvar occurs in ${varianceString(required)} position in type ${sym.info} of $sym" - if (ctx.scala2Mode && sym.owner.isConstructor) + if (ctx.scala2Mode && sym.owner.isConstructor) { ctx.migrationWarning(s"According to new variance rules, this is no longer accepted; need to annotate with @uncheckedVariance:\n$msg", sym.pos) + patch(Position(pos.end), " @scala.annotation.unchecked.uncheckedVariance") // TODO use an import or shorten if possible + } else ctx.error(msg, sym.pos) case None => } @@ -128,12 +132,11 @@ class VarianceChecker()(implicit ctx: Context) { case defn: MemberDef if skip => ctx.debuglog(s"Skipping variance check of ${sym.showDcl}") case tree: TypeDef => - checkVariance(sym) - traverseChildren(tree) + checkVariance(sym, tree.envelope) case tree: ValDef => - checkVariance(sym) + checkVariance(sym, tree.envelope) case DefDef(_, tparams, vparamss, _, _) => - checkVariance(sym) + checkVariance(sym, tree.envelope) tparams foreach traverse vparamss foreach (_ foreach traverse) case Template(_, _, _, body) => |