diff options
60 files changed, 2650 insertions, 251 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..2ab33a120 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 = { @@ -1000,7 +1006,7 @@ object desugar { collect(expr) case NamedArg(_, arg) => collect(arg) - case SeqLiteral(elems) => + case SeqLiteral(elems, _) => elems foreach collect case Alternative(trees) => for (tree <- trees; (vble, _) <- getVariables(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/Trees.scala b/src/dotty/tools/dotc/ast/Trees.scala index 54ace3be4..d0197b443 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 @@ -515,16 +519,18 @@ object Trees { type ThisTree[-T >: Untyped] = Try[T] } - /** Seq(elems) */ - case class SeqLiteral[-T >: Untyped] private[ast] (elems: List[Tree[T]]) + /** Seq(elems) + * @param tpt The element type of the sequence. + */ + case class SeqLiteral[-T >: Untyped] private[ast] (elems: List[Tree[T]], elemtpt: Tree[T]) extends Tree[T] { type ThisTree[-T >: Untyped] = SeqLiteral[T] } /** Array(elems) */ - class JavaSeqLiteral[T >: Untyped] private[ast] (elems: List[Tree[T]]) - extends SeqLiteral(elems) { - override def toString = s"JavaSeqLiteral($elems)" + class JavaSeqLiteral[T >: Untyped] private[ast] (elems: List[Tree[T]], elemtpt: Tree[T]) + extends SeqLiteral(elems, elemtpt) { + override def toString = s"JavaSeqLiteral($elems, $elemtpt)" } /** A type tree that represents an existing or inferred type */ @@ -974,12 +980,12 @@ object Trees { case tree: Try if (expr eq tree.expr) && (cases eq tree.cases) && (finalizer eq tree.finalizer) => tree case _ => finalize(tree, untpd.Try(expr, cases, finalizer)) } - def SeqLiteral(tree: Tree)(elems: List[Tree])(implicit ctx: Context): SeqLiteral = tree match { + def SeqLiteral(tree: Tree)(elems: List[Tree], elemtpt: Tree)(implicit ctx: Context): SeqLiteral = tree match { case tree: JavaSeqLiteral => - if (elems eq tree.elems) tree - else finalize(tree, new JavaSeqLiteral(elems)) - case tree: SeqLiteral if elems eq tree.elems => tree - case _ => finalize(tree, untpd.SeqLiteral(elems)) + if ((elems eq tree.elems) && (elemtpt eq tree.elemtpt)) tree + else finalize(tree, new JavaSeqLiteral(elems, elemtpt)) + case tree: SeqLiteral if (elems eq tree.elems) && (elemtpt eq tree.elemtpt) => tree + case _ => finalize(tree, untpd.SeqLiteral(elems, elemtpt)) } def TypeTree(tree: Tree)(original: Tree): TypeTree = tree match { case tree: TypeTree if original eq tree.original => tree @@ -1125,8 +1131,8 @@ object Trees { cpy.Return(tree)(transform(expr), transformSub(from)) case Try(block, cases, finalizer) => cpy.Try(tree)(transform(block), transformSub(cases), transform(finalizer)) - case SeqLiteral(elems) => - cpy.SeqLiteral(tree)(transform(elems)) + case SeqLiteral(elems, elemtpt) => + cpy.SeqLiteral(tree)(transform(elems), transform(elemtpt)) case TypeTree(original) => tree case SingletonTypeTree(ref) => @@ -1229,8 +1235,8 @@ object Trees { this(this(x, expr), from) case Try(block, handler, finalizer) => this(this(this(x, block), handler), finalizer) - case SeqLiteral(elems) => - this(x, elems) + case SeqLiteral(elems, elemtpt) => + this(this(x, elems), elemtpt) case TypeTree(original) => x case SingletonTypeTree(ref) => diff --git a/src/dotty/tools/dotc/ast/tpd.scala b/src/dotty/tools/dotc/ast/tpd.scala index bf6084d7a..a6d97478b 100644 --- a/src/dotty/tools/dotc/ast/tpd.scala +++ b/src/dotty/tools/dotc/ast/tpd.scala @@ -123,14 +123,11 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def Try(block: Tree, cases: List[CaseDef], finalizer: Tree)(implicit ctx: Context): Try = ta.assignType(untpd.Try(block, cases, finalizer), block, cases) - def SeqLiteral(elems: List[Tree])(implicit ctx: Context): SeqLiteral = - ta.assignType(untpd.SeqLiteral(elems), elems) + def SeqLiteral(elems: List[Tree], elemtpt: Tree)(implicit ctx: Context): SeqLiteral = + ta.assignType(untpd.SeqLiteral(elems, elemtpt), elems, elemtpt) - def SeqLiteral(tpe: Type, elems: List[Tree])(implicit ctx: Context): SeqLiteral = - if (tpe derivesFrom defn.SeqClass) SeqLiteral(elems) else JavaSeqLiteral(elems) - - def JavaSeqLiteral(elems: List[Tree])(implicit ctx: Context): SeqLiteral = - ta.assignType(new untpd.JavaSeqLiteral(elems), elems) + def JavaSeqLiteral(elems: List[Tree], elemtpt: Tree)(implicit ctx: Context): SeqLiteral = + ta.assignType(new untpd.JavaSeqLiteral(elems, elemtpt), elems, elemtpt) def TypeTree(original: Tree)(implicit ctx: Context): TypeTree = TypeTree(original.tpe, original) @@ -564,11 +561,14 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { } } - override def SeqLiteral(tree: Tree)(elems: List[Tree])(implicit ctx: Context): SeqLiteral = { - val tree1 = untpd.cpy.SeqLiteral(tree)(elems) + override def SeqLiteral(tree: Tree)(elems: List[Tree], elemtpt: Tree)(implicit ctx: Context): SeqLiteral = { + val tree1 = untpd.cpy.SeqLiteral(tree)(elems, elemtpt) tree match { - case tree: SeqLiteral if sameTypes(elems, tree.elems) => tree1.withTypeUnchecked(tree.tpe) - case _ => ta.assignType(tree1, elems) + case tree: SeqLiteral + if sameTypes(elems, tree.elems) && (elemtpt.tpe eq tree.elemtpt.tpe) => + tree1.withTypeUnchecked(tree.tpe) + case _ => + ta.assignType(tree1, elems, elemtpt) } } @@ -852,7 +852,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { ctx.typer.resolveOverloaded(allAlts, proto, Nil) assert(alternatives.size == 1, i"${if (alternatives.isEmpty) "no" else "multiple"} overloads available for " + - i"$method on ${receiver.tpe.widenDealias} with targs: $targs, args: $args and expectedType: $expectedType." + + i"$method on ${receiver.tpe.widenDealias} with targs: $targs%, %; args: $args%, % of types ${args.tpes}%, %; expectedType: $expectedType." + i" isAnnotConstructor = $isAnnotConstructor.\n" + i"all alternatives: ${allAlts.map(_.symbol.showDcl).mkString(", ")}\n" + i"matching alternatives: ${alternatives.map(_.symbol.showDcl).mkString(", ")}.") // this is parsed from bytecode tree. there's nothing user can do about it diff --git a/src/dotty/tools/dotc/ast/untpd.scala b/src/dotty/tools/dotc/ast/untpd.scala index 85052a4da..c7a7036c3 100644 --- a/src/dotty/tools/dotc/ast/untpd.scala +++ b/src/dotty/tools/dotc/ast/untpd.scala @@ -127,8 +127,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def CaseDef(pat: Tree, guard: Tree, body: Tree): CaseDef = new CaseDef(pat, guard, body) def Return(expr: Tree, from: Tree): Return = new Return(expr, from) def Try(expr: Tree, cases: List[CaseDef], finalizer: Tree): Try = new Try(expr, cases, finalizer) - def SeqLiteral(elems: List[Tree]): SeqLiteral = new SeqLiteral(elems) - def JavaSeqLiteral(elems: List[Tree]): JavaSeqLiteral = new JavaSeqLiteral(elems) + def SeqLiteral(elems: List[Tree], elemtpt: Tree): SeqLiteral = new SeqLiteral(elems, elemtpt) + def JavaSeqLiteral(elems: List[Tree], elemtpt: Tree): JavaSeqLiteral = new JavaSeqLiteral(elems, elemtpt) def TypeTree(original: Tree): TypeTree = new TypeTree(original) def TypeTree() = new TypeTree(EmptyTree) def SingletonTypeTree(ref: Tree): SingletonTypeTree = new SingletonTypeTree(ref) 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 b1d53c90d..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 */ 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/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/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index bde8cc10a..a83e7726a 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -616,7 +616,7 @@ object SymDenotations { /** Is this symbol a class references to which that are supertypes of null? */ final def isNullableClass(implicit ctx: Context): Boolean = - isClass && !isValueClass && !(this is ModuleClass) + isClass && !isValueClass && !(this is ModuleClass) && symbol != defn.NothingClass /** Is this definition accessible as a member of tree with type `pre`? * @param pre The type of the tree from which the selection is made 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 17c2ec4ca..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 diff --git a/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index 9ea24324b..25558a79a 100644 --- a/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -438,7 +438,14 @@ class ClassfileParser( case None => hasError = true } if (hasError) None - else if (skip) None else Some(JavaSeqLiteral(arr.toList)) + else if (skip) None + else { + val elems = arr.toList + val elemType = + if (elems.isEmpty) defn.ObjectType + else ctx.typeComparer.lub(elems.tpes).widen + Some(JavaSeqLiteral(elems, TypeTree(elemType))) + } case ANNOTATION_TAG => parseAnnotation(index, skip) map (_.tree) } diff --git a/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/src/dotty/tools/dotc/core/tasty/TreePickler.scala index bb6c3cd2e..f8f9c993f 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) => @@ -407,9 +418,9 @@ class TreePickler(pickler: TastyPickler) { case Try(block, cases, finalizer) => writeByte(TRY) withLength { pickleTree(block); cases.foreach(pickleTree); pickleTreeUnlessEmpty(finalizer) } - case SeqLiteral(elems) => + case SeqLiteral(elems, elemtpt) => writeByte(REPEATED) - withLength { elems.foreach(pickleTree) } + withLength { pickleTree(elemtpt); elems.foreach(pickleTree) } case TypeTree(original) => pickleTpt(tree) case Bind(name, body) => diff --git a/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 16caac02e..eb3369184 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 = @@ -801,7 +805,7 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { val fn = readTerm() val isJava = fn.tpe.isInstanceOf[JavaMethodType] def readArg() = readTerm() match { - case SeqLiteral(elems) if isJava => JavaSeqLiteral(elems) + case SeqLiteral(elems, elemtpt) if isJava => JavaSeqLiteral(elems, elemtpt) case arg => arg } tpd.Apply(fn, until(end)(readArg())) @@ -837,7 +841,8 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table) { case TRY => Try(readTerm(), readCases(end), ifBefore(end)(readTerm(), EmptyTree)) case REPEATED => - SeqLiteral(until(end)(readTerm())) + val elemtpt = readTpt() + SeqLiteral(until(end)(readTerm()), elemtpt) case BIND => val name = readName() val info = readType() diff --git a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index 7a13388ae..2831de3e0 100644 --- a/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -850,8 +850,8 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas val end = readNat() + readIndex // array elements are trees representing instances of scala.annotation.Annotation SeqLiteral( - defn.SeqType.appliedTo(defn.AnnotationType :: Nil), - until(end, () => readClassfileAnnotArg(readNat()))) + until(end, () => readClassfileAnnotArg(readNat())), + TypeTree(defn.AnnotationType)) } private def readAnnotInfoArg()(implicit ctx: Context): Tree = { @@ -1063,7 +1063,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas case ARRAYVALUEtree => val elemtpt = readTreeRef() val trees = until(end, readTreeRef) - SeqLiteral(defn.SeqType.appliedTo(elemtpt.tpe :: Nil), trees) + SeqLiteral(trees, elemtpt) // note can't deal with trees passed to Java methods as arrays here case FUNCTIONtree => 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/printing/RefinedPrinter.scala b/src/dotty/tools/dotc/printing/RefinedPrinter.scala index a03a01e8d..e21f12410 100644 --- a/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -380,8 +380,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { changePrec(GlobalPrec) { "throw " ~ toText(expr) } - case SeqLiteral(elems) => - "[" ~ toTextGlobal(elems, ",") ~ "]" + case SeqLiteral(elems, elemtpt) => + "[" ~ toTextGlobal(elems, ",") ~ " : " ~ toText(elemtpt) ~ "]" case tpt: untpd.DerivedTypeTree => "<derived typetree watching " ~ summarized(toText(tpt.watched)) ~ ">" case TypeTree(orig) => 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/ElimRepeated.scala b/src/dotty/tools/dotc/transform/ElimRepeated.scala index 98abbb26d..30778267d 100644 --- a/src/dotty/tools/dotc/transform/ElimRepeated.scala +++ b/src/dotty/tools/dotc/transform/ElimRepeated.scala @@ -71,8 +71,8 @@ class ElimRepeated extends MiniPhaseTransform with InfoTransformer with Annotati /** Convert sequence argument to Java array */ private def seqToArray(tree: Tree)(implicit ctx: Context): Tree = tree match { - case SeqLiteral(elems) => - JavaSeqLiteral(elems) + case SeqLiteral(elems, elemtpt) => + JavaSeqLiteral(elems, elemtpt) case _ => val elemType = tree.tpe.firstBaseArgInfo(defn.SeqClass) var elemClass = elemType.classSymbol 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 9170cd277..7ec0739c1 100644 --- a/src/dotty/tools/dotc/transform/ExplicitOuter.scala +++ b/src/dotty/tools/dotc/transform/ExplicitOuter.scala @@ -326,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/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/SeqLiterals.scala b/src/dotty/tools/dotc/transform/SeqLiterals.scala index 6e29d7e09..49ea69530 100644 --- a/src/dotty/tools/dotc/transform/SeqLiterals.scala +++ b/src/dotty/tools/dotc/transform/SeqLiterals.scala @@ -32,9 +32,9 @@ class SeqLiterals extends MiniPhaseTransform { override def transformSeqLiteral(tree: SeqLiteral)(implicit ctx: Context, info: TransformerInfo): Tree = tree match { case tree: JavaSeqLiteral => tree case _ => - val arr = JavaSeqLiteral(tree.elems) + val arr = JavaSeqLiteral(tree.elems, tree.elemtpt) //println(i"trans seq $tree, arr = $arr: ${arr.tpe} ${arr.tpe.elemType}") - val elemtp = arr.tpe.elemType.bounds.hi + val elemtp = tree.elemtpt.tpe val elemCls = elemtp.classSymbol val (wrapMethStr, targs) = if (elemCls.isPrimitiveValueClass) (s"wrap${elemCls.name}Array", Nil) diff --git a/src/dotty/tools/dotc/transform/TreeTransform.scala b/src/dotty/tools/dotc/transform/TreeTransform.scala index 7a2280baa..7fe003388 100644 --- a/src/dotty/tools/dotc/transform/TreeTransform.scala +++ b/src/dotty/tools/dotc/transform/TreeTransform.scala @@ -1148,7 +1148,8 @@ object TreeTransforms { if (mutatedInfo eq null) tree else { val elems = transformTrees(tree.elems, mutatedInfo, cur) - goSeqLiteral(cpy.SeqLiteral(tree)(elems), mutatedInfo.nx.nxTransSeqLiteral(cur)) + val elemtpt = transform(tree.elemtpt, mutatedInfo, cur) + goSeqLiteral(cpy.SeqLiteral(tree)(elems, elemtpt), mutatedInfo.nx.nxTransSeqLiteral(cur)) } case tree: TypeTree => implicit val mutatedInfo: TransformerInfo = mutateTransformers(info, prepForTypeTree, info.nx.nxPrepTypeTree, tree, cur) diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 3b8c56ea6..f3903e539 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -442,7 +442,10 @@ trait Applications extends Compatibility { self: Typer => def makeVarArg(n: Int, elemFormal: Type): Unit = { val args = typedArgBuf.takeRight(n).toList typedArgBuf.trimEnd(n) - val seqLit = if (methodType.isJava) JavaSeqLiteral(args) else SeqLiteral(args) + val elemtpt = TypeTree(elemFormal) + val seqLit = + if (methodType.isJava) JavaSeqLiteral(args, elemtpt) + else SeqLiteral(args, elemtpt) typedArgBuf += seqToRepeated(seqLit) } @@ -771,7 +774,7 @@ trait Applications extends Compatibility { self: Typer => for (argType <- argTypes) assert(!argType.isInstanceOf[TypeBounds], unapplyApp.tpe.show) val bunchedArgs = argTypes match { case argType :: Nil => - if (argType.isRepeatedParam) untpd.SeqLiteral(args) :: Nil + if (argType.isRepeatedParam) untpd.SeqLiteral(args, untpd.TypeTree()) :: Nil else if (args.lengthCompare(1) > 0 && ctx.canAutoTuple) untpd.Tuple(args) :: Nil else args case _ => args @@ -1089,7 +1092,10 @@ trait Applications extends Compatibility { self: Typer => val alts2 = narrowByShapes(alts1) //ctx.log(i"narrowed by shape: ${alts1.map(_.symbol.showDcl)}%, %") if (isDetermined(alts2)) alts2 - else narrowByTrees(alts2, pt.typedArgs, resultType) + else { + pretypeArgs(alts2, pt) + narrowByTrees(alts2, pt.typedArgs, resultType) + } } case pt @ PolyProto(targs, pt1) => @@ -1122,6 +1128,69 @@ trait Applications extends Compatibility { self: Typer => } } + /** Try to typecheck any arguments in `pt` that are function values missing a + * parameter type. The expected type for these arguments is the lub of the + * corresponding formal parameter types of all alternatives. Type variables + * in formal parameter types are replaced by wildcards. The result of the + * typecheck is stored in `pt`, to be retrieved when its `typedArgs` are selected. + * The benefit of doing this is to allow idioms like this: + * + * def map(f: Char => Char): String = ??? + * def map[U](f: Char => U): Seq[U] = ??? + * map(x => x.toUpper) + * + * Without `pretypeArgs` we'd get a "missing parameter type" error for `x`. + * With `pretypeArgs`, we use the union of the two formal parameter types + * `Char => Char` and `Char => ?` as the expected type of the closure `x => x.toUpper`. + * That union is `Char => Char`, so we have an expected parameter type `Char` + * for `x`, and the code typechecks. + */ + private def pretypeArgs(alts: List[TermRef], pt: FunProto)(implicit ctx: Context): Unit = { + def recur(altFormals: List[List[Type]], args: List[untpd.Tree]): Unit = args match { + case arg :: args1 if !altFormals.exists(_.isEmpty) => + def isUnknownParamType(t: untpd.Tree) = t match { + case ValDef(_, tpt, _) => tpt.isEmpty + case _ => false + } + arg match { + case arg: untpd.Function if arg.args.exists(isUnknownParamType) => + def isUniform[T](xs: List[T])(p: (T, T) => Boolean) = xs.forall(p(_, xs.head)) + val formalsForArg: List[Type] = altFormals.map(_.head) + // For alternatives alt_1, ..., alt_n, test whether formal types for current argument are of the form + // (p_1_1, ..., p_m_1) => r_1 + // ... + // (p_1_n, ..., p_m_n) => r_n + val decomposedFormalsForArg: List[Option[(List[Type], Type)]] = + formalsForArg.map(defn.FunctionOf.unapply) + if (decomposedFormalsForArg.forall(_.isDefined)) { + val formalParamTypessForArg: List[List[Type]] = + decomposedFormalsForArg.map(_.get._1) + if (isUniform(formalParamTypessForArg)((x, y) => x.length == y.length)) { + val commonParamTypes = formalParamTypessForArg.transpose.map(ps => + // Given definitions above, for i = 1,...,m, + // ps(i) = List(p_i_1, ..., p_i_n) -- i.e. a column + // If all p_i_k's are the same, assume the type as formal parameter + // type of the i'th parameter of the closure. + if (isUniform(ps)(ctx.typeComparer.isSameTypeWhenFrozen(_, _))) ps.head + else WildcardType) + val commonFormal = defn.FunctionOf(commonParamTypes, WildcardType) + overload.println(i"pretype arg $arg with expected type $commonFormal") + pt.typedArg(arg, commonFormal) + } + } + case _ => + } + recur(altFormals.map(_.tail), args1) + case _ => + } + def paramTypes(alt: Type): List[Type] = alt match { + case mt: MethodType => mt.paramTypes + case mt: PolyType => paramTypes(mt.resultType) + case _ => Nil + } + recur(alts.map(alt => paramTypes(alt.widen)), pt.args) + } + private def harmonizeWith[T <: AnyRef](ts: List[T])(tpe: T => Type, adapt: (T, Type) => T)(implicit ctx: Context): List[T] = { def numericClasses(ts: List[T], acc: Set[Symbol]): Set[Symbol] = ts match { case t :: ts1 => diff --git a/src/dotty/tools/dotc/typer/ProtoTypes.scala b/src/dotty/tools/dotc/typer/ProtoTypes.scala index 97b47b2bd..740258821 100644 --- a/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -179,7 +179,7 @@ object ProtoTypes { if ((args eq this.args) && (resultType eq this.resultType) && (typer eq this.typer)) this else new FunProto(args, resultType, typer) - def argsAreTyped: Boolean = myTypedArgs.nonEmpty || args.isEmpty + def argsAreTyped: Boolean = myTypedArgs.size == args.length /** The typed arguments. This takes any arguments already typed using * `typedArg` into account. diff --git a/src/dotty/tools/dotc/typer/ReTyper.scala b/src/dotty/tools/dotc/typer/ReTyper.scala index 49718fd00..225451886 100644 --- a/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/src/dotty/tools/dotc/typer/ReTyper.scala @@ -92,7 +92,7 @@ class ReTyper extends Typer { try super.typedUnadapted(tree, pt) catch { case NonFatal(ex) => - Printers.transforms.println(i"exception while typing $tree of class ${tree.getClass} # ${tree.uniqueId}") + println(i"exception while typing $tree of class ${tree.getClass} # ${tree.uniqueId}") throw ex } diff --git a/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala index 476839ab3..84951fd2b 100644 --- a/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -392,14 +392,12 @@ trait TypeAssigner { if (cases.isEmpty) tree.withType(expr.tpe) else tree.withType(ctx.typeComparer.lub(expr.tpe :: cases.tpes)) - def assignType(tree: untpd.SeqLiteral, elems: List[Tree])(implicit ctx: Context) = tree match { - case tree: JavaSeqLiteral => - tree.withType(defn.ArrayOf(ctx.typeComparer.lub(elems.tpes).widen)) - case _ => - val ownType = - if (ctx.erasedTypes) defn.SeqType - else defn.SeqType.appliedTo(ctx.typeComparer.lub(elems.tpes).widen) - tree.withType(ownType) + def assignType(tree: untpd.SeqLiteral, elems: List[Tree], elemtpt: Tree)(implicit ctx: Context) = { + val ownType = tree match { + case tree: JavaSeqLiteral => defn.ArrayOf(elemtpt.tpe) + case _ => if (ctx.erasedTypes) defn.SeqType else defn.SeqType.appliedTo(elemtpt.tpe) + } + tree.withType(ownType) } def assignType(tree: untpd.SingletonTypeTree, ref: Tree)(implicit ctx: Context) = diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index fdb92a40b..8189f3c67 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 { @@ -836,7 +839,14 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typedSeqLiteral(tree: untpd.SeqLiteral, pt: Type)(implicit ctx: Context): SeqLiteral = track("typedSeqLiteral") { val proto1 = pt.elemType orElse WildcardType val elems1 = tree.elems mapconserve (typed(_, proto1)) - assignType(cpy.SeqLiteral(tree)(elems1), elems1) + val proto2 = // the computed type of the `elemtpt` field + if (!tree.elemtpt.isEmpty) WildcardType + else if (isFullyDefined(proto1, ForceDegree.none)) proto1 + else if (tree.elems.isEmpty && tree.isInstanceOf[Trees.JavaSeqLiteral[_]]) + defn.ObjectType // generic empty Java varargs are of type Object[] + else ctx.typeComparer.lub(elems1.tpes) + val elemtpt1 = typed(tree.elemtpt, proto2) + assignType(cpy.SeqLiteral(tree)(elems1, elemtpt1), elems1, elemtpt1) } def typedTypeTree(tree: untpd.TypeTree, pt: Type)(implicit ctx: Context): TypeTree = track("typedTypeTree") { @@ -984,7 +994,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") { @@ -1135,13 +1156,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) @@ -1232,7 +1257,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) => diff --git a/src/strawman/collections/CollectionStrawMan4.scala b/src/strawman/collections/CollectionStrawMan4.scala new file mode 100644 index 000000000..9159b1cfc --- /dev/null +++ b/src/strawman/collections/CollectionStrawMan4.scala @@ -0,0 +1,486 @@ +package strawman.collections + +import Predef.{augmentString => _, wrapString => _, _} +import scala.reflect.ClassTag + +/** A strawman architecture for new collections. It contains some + * example collection classes and methods with the intent to expose + * some key issues. It would be good to compare this to other + * implementations of the same functionality, to get an idea of the + * strengths and weaknesses of different collection architectures. + * + * For a test file, see tests/run/CollectionTests.scala. + */ +object CollectionStrawMan4 { + + /* ------------ Base Traits -------------------------------- */ + + /** Iterator can be used only once */ + trait IterableOnce[+A] { + def iterator: Iterator[A] + } + + /** Base trait for generic collections */ + trait Iterable[+A] extends IterableOnce[A] with FromIterable[Iterable] { + def iterator: Iterator[A] + def view: View[A] = View.fromIterator(iterator) + def knownLength: Int = -1 + } + + /** Base trait for instances that can construct a collection from an iterator */ + trait FromIterable[+C[X] <: Iterable[X]] { + def fromIterable[B](v: Iterable[B]): C[B] + } + + /** Base trait for companion objects of collections */ + trait IterableFactory[+C[X] <: Iterable[X]] extends FromIterable[C] { + def empty[X]: C[X] = fromIterable(View.Empty) + def apply[A](xs: A*): C[A] = fromIterable(View.Elems(xs: _*)) + } + + /** Base trait for sequence collections */ + trait Seq[+A] extends Iterable[A] with FromIterable[Seq] { + def apply(i: Int): A + def length: Int + } + + trait Builder[-A, +To] { + def +=(x: A): this.type + def ++=(xs: IterableOnce[A]): Unit = xs.iterator.foreach(+=) + def result: To + } + + /* ------------ Operations ----------------------------------- */ + + /** Operations returning types unrelated to current collection */ + trait Ops[A] extends Any { + def iterator: Iterator[A] + def foreach(f: A => Unit): Unit = iterator.foreach(f) + def foldLeft[B](z: B)(op: (B, A) => B): B = iterator.foldLeft(z)(op) + def foldRight[B](z: B)(op: (A, B) => B): B = iterator.foldRight(z)(op) + def indexWhere(p: A => Boolean): Int = iterator.indexWhere(p) + def isEmpty: Boolean = !iterator.hasNext + def head: A = iterator.next + } + + /** Transforms returning same collection type */ + trait MonoTransforms[A, Repr] extends Any { + protected def coll: Iterable[A] + protected def fromIterable(it: Iterable[A]): Repr + def filter(p: A => Boolean): Repr = fromIterable(View.Filter(coll, p)) + def partition(p: A => Boolean): (Repr, Repr) = { + val pn = View.Partition(coll, p) + (fromIterable(pn.left), fromIterable(pn.right)) + } + def drop(n: Int): Repr = fromIterable(View.Drop(coll, n)) + def to[C[X] <: Iterable[X]](fv: FromIterable[C]): C[A] = fv.fromIterable(coll) + } + + trait PolyTransforms[A, C[X]] extends Any { + protected def coll: Iterable[A] + protected def fromIterable[B](it: Iterable[B]): C[B] + def map[B](f: A => B): C[B] = fromIterable(View.Map(coll, f)) + def flatMap[B](f: A => IterableOnce[B]): C[B] = fromIterable(View.FlatMap(coll, f)) + def ++[B >: A](xs: IterableOnce[B]): C[B] = fromIterable(View.Concat(coll, xs)) + def zip[B](xs: IterableOnce[B]): C[(A, B)] = fromIterable(View.Zip(coll, xs)) + } + + /** Transforms that only apply to Seq */ + trait MonoTransformsOfSeqs[A, Repr] extends Any with MonoTransforms[A, Repr] { + def reverse: Repr = fromIterable(View.Reverse(coll)) + } + + /** Implementation of Ops for all generic collections */ + implicit class IterableOps[A](val c: Iterable[A]) + extends AnyVal with Ops[A] { + def iterator = c.iterator + } + + /** Implementation of MonoTransforms for all generic collections */ + implicit class IterableMonoTransforms[A, C[X] <: Iterable[X]](val c: Iterable[A] with FromIterable[C]) + extends AnyVal with MonoTransforms[A, C[A]] { + protected def coll = c + protected def fromIterable(it: Iterable[A]): C[A] = c.fromIterable(it) + } + + /** Implementation of PolyTransforms for all generic collections */ + implicit class IterablePolyTransforms[A, C[X] <: Iterable[X]](val c: Iterable[A] with FromIterable[C]) + extends AnyVal with PolyTransforms[A, C] { + protected def coll = c + protected def fromIterable[B](it: Iterable[B]): C[B] = c.fromIterable(it) + } + + /** Implementation of MonoTransformsForSeqs for all generic collections */ + implicit class SeqMonoTransforms[A, C[X] <: Seq[X]](val c: Seq[A] with FromIterable[C]) + extends AnyVal with MonoTransformsOfSeqs[A, C[A]] { + protected def coll = c + protected def fromIterable(it: Iterable[A]): C[A] = c.fromIterable(it) + } + + /* --------- Concrete collection types ------------------------------- */ + + /** Concrete collection type: List */ + sealed trait List[+A] extends Seq[A] with FromIterable[List] { self => + def isEmpty: Boolean + def head: A + def tail: List[A] + def iterator = new Iterator[A] { + private[this] var current = self + def hasNext = !current.isEmpty + def next = { val r = current.head; current = current.tail; r } + } + def fromIterable[B](c: Iterable[B]): List[B] = List.fromIterable(c) + def apply(i: Int): A = { + require(!isEmpty) + if (i == 0) head else tail.apply(i - 1) + } + def :::[B >: A](prefix: List[B]): List[B] = + if (prefix.isEmpty) this + else Cons(prefix.head, prefix.tail ::: this) + def length: Int = + if (isEmpty) 0 else 1 + tail.length + } + + case class Cons[+A](x: A, xs: List[A]) extends List[A] { + def isEmpty = false + def head = x + def tail = xs + } + + case object Nil extends List[Nothing] { + def isEmpty = true + def head = ??? + def tail = ??? + } + + object List extends IterableFactory[List] { + def fromIterator[B](it: Iterator[B]): List[B] = + if (it.hasNext) Cons(it.next, fromIterator(it)) else Nil + def fromIterable[B](c: Iterable[B]): List[B] = c match { + case View.Concat(xs, ys: Iterable[B]) => + fromIterable(xs) ::: fromIterable(ys) + case View.Drop(xs: List[B], n) => + var i = 0 + var ys = xs + while (i < n && !xs.isEmpty) { + ys = ys.tail + i += 1 + } + ys + case _ => fromIterator(c.iterator) + } + } + + /** Concrete collection type: ArrayBuffer */ + class ArrayBuffer[A] private (initElems: Array[AnyRef], initLength: Int) + extends Seq[A] with FromIterable[ArrayBuffer] with Builder[A, ArrayBuffer[A]] { + def this() = this(new Array[AnyRef](16), 0) + private var elems: Array[AnyRef] = initElems + private var start = 0 + private var end = initLength + def apply(n: Int) = elems(start + n).asInstanceOf[A] + def length = end - start + override def knownLength = length + override def view = new ArrayBufferView(elems, start, end) + def iterator = view.iterator + def fromIterable[B](it: Iterable[B]): ArrayBuffer[B] = + ArrayBuffer.fromIterable(it) + def +=(elem: A): this.type = { + if (end == elems.length) { + if (start > 0) { + Array.copy(elems, start, elems, 0, length) + end -= start + start = 0 + } + else { + val newelems = new Array[AnyRef](end * 2) + Array.copy(elems, 0, newelems, 0, end) + elems = newelems + } + } + elems(end) = elem.asInstanceOf[AnyRef] + end += 1 + this + } + def result = this + def trimStart(n: Int): Unit = start += (n max 0) + override def toString = s"ArrayBuffer(${elems.slice(start, end).mkString(", ")})" + } + + object ArrayBuffer extends IterableFactory[ArrayBuffer] { + def fromIterable[B](c: Iterable[B]): ArrayBuffer[B] = c match { + case View.Concat(fst: ArrayBuffer[B], snd: ArrayBuffer[B]) => + val elems = new Array[AnyRef](fst.length + snd.length) + Array.copy(fst.elems, fst.start, elems, 0, fst.length) + Array.copy(snd.elems, snd.start, elems, fst.length, snd.length) + new ArrayBuffer(elems, elems.length) + case pd @ View.Partitioned(partition: View.Partition[B]) => + partition.distribute(new ArrayBuffer[B]()) + pd.forced.get.asInstanceOf[ArrayBuffer[B]] + case c if c.knownLength >= 0 => + val elems = new Array[AnyRef](c.knownLength) + val it = c.iterator + for (i <- 0 until elems.length) elems(i) = it.next().asInstanceOf[AnyRef] + new ArrayBuffer[B](elems, elems.length) + case _ => + val buf = new ArrayBuffer[B] + val it = c.iterator + while (it.hasNext) buf += it.next() + buf + } + } + + class ArrayBufferView[A](val elems: Array[AnyRef], val start: Int, val end: Int) extends RandomAccessView[A] { + def apply(n: Int) = elems(start + n).asInstanceOf[A] + } + + case class StringView(s: String) extends RandomAccessView[Char] { + val start = 0 + val end = s.length + def apply(n: Int) = s.charAt(n) + } + + /** Concrete collection type: String */ + implicit class StringOps(val s: String) extends AnyVal with Ops[Char] { + def iterator: Iterator[Char] = new StringView(s).iterator + } + + implicit class StringMonoTransforms(val s: String) + extends AnyVal with MonoTransformsOfSeqs[Char, String] { + protected def coll: Iterable[Char] = StringView(s) + protected def fromIterable(it: Iterable[Char]): String = { + val sb = new StringBuilder + for (ch <- it) sb.append(ch) + sb.toString + } + } + + implicit class StringPolyTransforms(val s: String) + extends AnyVal with PolyTransforms[Char, Seq] { + protected def coll = StringView(s) + protected def fromIterable[B](it: Iterable[B]): Seq[B] = List.fromIterable(it) + def map(f: Char => Char): String = { + val sb = new StringBuilder + for (ch <- s) sb.append(f(ch)) + sb.toString + } + def flatMap(f: Char => String) = { + val sb = new StringBuilder + for (ch <- s) sb.append(f(ch)) + sb.toString + } + def ++(xs: IterableOnce[Char]): String = { + val sb = new StringBuilder(s) + for (ch <- xs.iterator) sb.append(ch) + sb.toString + } + def ++(xs: String): String = s + xs + } + + /* ------------ Views --------------------------------------- */ + + /** A lazy iterable */ + trait View[+A] extends Iterable[A] with FromIterable[View] { + override def view = this + override def fromIterable[B](c: Iterable[B]) = c match { + case c: View[B] => c + case _ => View.fromIterator(c.iterator) + } + } + + /** Iterator defined in terms of indexing a range */ + trait RandomAccessView[+A] extends View[A] { + def start: Int + def end: Int + def apply(i: Int): A + def iterator: Iterator[A] = new Iterator[A] { + private var current = start + def hasNext = current < end + def next: A = { + val r = apply(current) + current += 1 + r + } + } + override def knownLength = end - start max 0 + } + + object View { + def fromIterator[A](it: => Iterator[A]): View[A] = new View[A] { + def iterator = it + } + case object Empty extends View[Nothing] { + def iterator = Iterator.empty + override def knownLength = 0 + } + case class Elems[A](xs: A*) extends View[A] { + def iterator = Iterator(xs: _*) + override def knownLength = xs.length + } + case class Filter[A](val underlying: Iterable[A], p: A => Boolean) extends View[A] { + def iterator = underlying.iterator.filter(p) + } + case class Partition[A](val underlying: Iterable[A], p: A => Boolean) { + val left, right = Partitioned(this) + def distribute(bf: => Builder[A, Iterable[A]]) = { + val lb, rb = bf + val it = underlying.iterator + while (it.hasNext) { + val x = it.next() + (if (p(x)) lb else rb) += x + } + left.forced = Some(lb.result) + right.forced = Some(rb.result) + } + } + case class Partitioned[A](partition: Partition[A]) extends View[A] { + private var myForced: Option[Iterable[A]] = None + def forced: Option[Iterable[A]] = myForced + private[View] def forced_=(x: Option[Iterable[A]]): Unit = myForced = x + def underlying = partition.underlying + def iterator = forced match { + case Some(c) => c.iterator + case None => + underlying.iterator.filter( + if (this eq partition.left) partition.p else !partition.p(_)) + } + } + case class Drop[A](underlying: Iterable[A], n: Int) extends View[A] { + def iterator = underlying.iterator.drop(n) + override def knownLength = + if (underlying.knownLength >= 0) underlying.knownLength - n max 0 else -1 + } + case class Map[A, B](underlying: Iterable[A], f: A => B) extends View[B] { + def iterator = underlying.iterator.map(f) + override def knownLength = underlying.knownLength + } + case class FlatMap[A, B](underlying: Iterable[A], f: A => IterableOnce[B]) extends View[B] { + def iterator = underlying.iterator.flatMap(f) + } + case class Concat[A](underlying: Iterable[A], other: IterableOnce[A]) extends View[A] { + def iterator = underlying.iterator ++ other + override def knownLength = other match { + case other: Iterable[_] if underlying.knownLength >= 0 && other.knownLength >= 0 => + underlying.knownLength + other.knownLength + case _ => + -1 + } + } + case class Zip[A, B](underlying: Iterable[A], other: IterableOnce[B]) extends View[(A, B)] { + def iterator = underlying.iterator.zip(other) + override def knownLength = other match { + case other: Iterable[_] if underlying.knownLength >= 0 && other.knownLength >= 0 => + underlying.knownLength min other.knownLength + case _ => + -1 + } + } + case class Reverse[A](underlying: Iterable[A]) extends View[A] { + def iterator = { + var xs: List[A] = Nil + val it = underlying.iterator + while (it.hasNext) xs = Cons(it.next(), xs) + xs.iterator + } + override def knownLength = underlying.knownLength + } + } + +/* ---------- Iterators ---------------------------------------------------*/ + + /** A core Iterator class */ + trait Iterator[+A] extends IterableOnce[A] { self => + def hasNext: Boolean + def next(): A + def iterator = this + def foldLeft[B](z: B)(op: (B, A) => B): B = + if (hasNext) foldLeft(op(z, next))(op) else z + def foldRight[B](z: B)(op: (A, B) => B): B = + if (hasNext) op(next(), foldRight(z)(op)) else z + def foreach(f: A => Unit): Unit = + while (hasNext) f(next()) + def indexWhere(p: A => Boolean): Int = { + var i = 0 + while (hasNext) { + if (p(next())) return i + i += 1 + } + -1 + } + def filter(p: A => Boolean): Iterator[A] = new Iterator[A] { + private var hd: A = _ + private var hdDefined: Boolean = false + + def hasNext: Boolean = hdDefined || { + do { + if (!self.hasNext) return false + hd = self.next() + } while (!p(hd)) + hdDefined = true + true + } + + def next() = + if (hasNext) { + hdDefined = false + hd + } + else Iterator.empty.next() + } + + def map[B](f: A => B): Iterator[B] = new Iterator[B] { + def hasNext = self.hasNext + def next() = f(self.next()) + } + + def flatMap[B](f: A => IterableOnce[B]): Iterator[B] = new Iterator[B] { + private var myCurrent: Iterator[B] = Iterator.empty + private def current = { + while (!myCurrent.hasNext && self.hasNext) + myCurrent = f(self.next()).iterator + myCurrent + } + def hasNext = current.hasNext + def next() = current.next() + } + def ++[B >: A](xs: IterableOnce[B]): Iterator[B] = new Iterator[B] { + private var myCurrent: Iterator[B] = self + private var first = true + private def current = { + if (!myCurrent.hasNext && first) { + myCurrent = xs.iterator + first = false + } + myCurrent + } + def hasNext = current.hasNext + def next() = current.next() + } + def drop(n: Int): Iterator[A] = { + var i = 0 + while (i < n && hasNext) { + next() + i += 1 + } + this + } + def zip[B](that: IterableOnce[B]): Iterator[(A, B)] = new Iterator[(A, B)] { + val thatIterator = that.iterator + def hasNext = self.hasNext && thatIterator.hasNext + def next() = (self.next(), thatIterator.next()) + } + } + + object Iterator { + val empty: Iterator[Nothing] = new Iterator[Nothing] { + def hasNext = false + def next = throw new NoSuchElementException("next on empty iterator") + } + def apply[A](xs: A*): Iterator[A] = new RandomAccessView[A] { + val start = 0 + val end = xs.length + def apply(n: Int) = xs(n) + }.iterator + } +} + diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 2d4ed289c..457116feb 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -104,6 +104,8 @@ class tests extends CompilerTest { @Test def pos_scala2_all = compileFiles(posScala2Dir, scala2mode) + @Test def rewrites = compileFile(posScala2Dir, "rewrites", "-rewrite" :: scala2mode) + @Test def pos_859 = compileFile(posSpecialDir, "i859", scala2mode)(allowDeepSubtypes) @Test def new_all = compileFiles(newDir, twice) diff --git a/test/test/CompilerTest.scala b/test/test/CompilerTest.scala index 678ef74b1..ef2f719fc 100644 --- a/test/test/CompilerTest.scala +++ b/test/test/CompilerTest.scala @@ -10,6 +10,7 @@ import scala.reflect.io.{ Path, Directory, File => SFile, AbstractFile } import scala.tools.partest.nest.{ FileManager, NestUI } import scala.annotation.tailrec import java.io.{ RandomAccessFile, File => JFile } +import dotty.tools.io.PlainFile import org.junit.Test @@ -89,7 +90,20 @@ abstract class CompilerTest { if (!generatePartestFiles || !partestableFile(prefix, fileName, extension, args ++ defaultOptions)) { if (runTest) log(s"WARNING: run tests can only be run by partest, JUnit just verifies compilation: $prefix$fileName$extension") - compileArgs((s"$filePath" :: args).toArray, expErrors) + if (args.contains("-rewrite")) { + val file = new PlainFile(filePath) + val data = file.toByteArray + // compile with rewrite + compileArgs((filePath :: args).toArray, expErrors) + // compile again, check that file now compiles without -language:Scala2 + val plainArgs = args.filter(arg => arg != "-rewrite" && arg != "-language:Scala2") + compileFile(prefix, fileName, plainArgs, extension, runTest) + // restore original test file + val out = file.output + out.write(data) + out.close() + } + else compileArgs((filePath :: args).toArray, expErrors) } else { val kind = testKind(prefix, runTest) log(s"generating partest files for test file: $prefix$fileName$extension of kind $kind") @@ -191,6 +205,8 @@ abstract class CompilerTest { } } + + // ========== HELPERS ============= private def expectedErrors(filePaths: List[String]): List[ErrorsInFile] = if (filePaths.exists(isNegTest(_))) filePaths.map(getErrors(_)) else Nil diff --git a/test/test/DeSugarTest.scala b/test/test/DeSugarTest.scala index 44360d657..77aa293d5 100644 --- a/test/test/DeSugarTest.scala +++ b/test/test/DeSugarTest.scala @@ -53,8 +53,8 @@ class DeSugarTest extends ParserTest { cpy.Typed(tree1)(transform(expr), transform(tpt, Type)) case CaseDef(pat, guard, body) => cpy.CaseDef(tree1)(transform(pat, Pattern), transform(guard), transform(body)) - case SeqLiteral(elems) => - cpy.SeqLiteral(tree1)(transform(elems)) + case SeqLiteral(elems, elemtpt) => + cpy.SeqLiteral(tree1)(transform(elems), transform(elemtpt)) case UnApply(fun, implicits, patterns) => cpy.UnApply(tree1)(transform(fun, Expr), transform(implicits), transform(patterns)) case tree1 @ ValDef(name, tpt, _) => diff --git a/tests/neg/classOf.scala b/tests/neg/classOf.scala new file mode 100644 index 000000000..e13cf71c4 --- /dev/null +++ b/tests/neg/classOf.scala @@ -0,0 +1,11 @@ +object Test { + + class C { type I } + type A = C + + def f1[T] = classOf[T] // error + def f2[T <: String] = classOf[T] // error + val x = classOf[Test.type] // error + val y = classOf[C { type I = String }] // error + val z = classOf[A] // ok +} diff --git a/tests/neg/overloaded.scala b/tests/neg/overloaded.scala new file mode 100644 index 000000000..ce971ebcf --- /dev/null +++ b/tests/neg/overloaded.scala @@ -0,0 +1,17 @@ +// testing the limits of parameter type inference + +object Test { + def mapX(f: Char => Char): String = ??? + def mapX[U](f: U => U): U = ??? + mapX(x => x) // error: missing parameter type + + def foo(f: Char => Char): Unit = ??? + def foo(f: Int => Int): String = ??? + foo(x => x) // error: missing parameter type + + def bar(f: (Char, Char) => Unit): Unit = ??? + def bar(f: Char => Unit) = ??? + bar((x, y) => ()) + bar (x => ()) + +} diff --git a/tests/neg/tate.scala b/tests/neg/tate.scala new file mode 100644 index 000000000..acf7ee7e3 --- /dev/null +++ b/tests/neg/tate.scala @@ -0,0 +1,11 @@ + object unsound { + trait Bound[A, B <: A] + trait Bind[A] { + def bad[B <: A](bound: Bound[A, B], b: B) = b + } + def coerce[T, U](t: T): U = { + lazy val bound: Bound[U, _ >: T] = ??? // error: >: T does not conform to upper bound + def bind = new Bind[U] {} + bind.bad(bound, t) + } + } diff --git a/tests/pending/neg/tate.scala b/tests/pending/neg/tate.scala new file mode 100644 index 000000000..d626ccd3f --- /dev/null +++ b/tests/pending/neg/tate.scala @@ -0,0 +1,11 @@ +trait Out[+T] + +object Test { + + def foo[T <% AnyRef](x: T) = ??? + + var x: Out[_ >: String] = ??? + var y: Out[String] = ??? + x = y // should give error, but currently masked by covariant alias representation + // y = x +} diff --git a/tests/pending/pos/lazyvals.scala b/tests/pending/pos/lazyvals.scala new file mode 100644 index 000000000..93a82cd0c --- /dev/null +++ b/tests/pending/pos/lazyvals.scala @@ -0,0 +1,18 @@ + + +trait Iterator { + + def span() = { + val self: Int = 33 + class Leading { + def finish(): Unit = println("finished") + } + val leading = new Leading + + class Trailing { + @volatile lazy val it = leading.finish() + } + val trailing = new Trailing + (leading, trailing) + } +} diff --git a/tests/pickling/hk.scala b/tests/pickling/hk.scala index 9fdaf94f6..a8f2aa597 100644 --- a/tests/pickling/hk.scala +++ b/tests/pickling/hk.scala @@ -31,7 +31,7 @@ object higherKinded { } class Ident[-T >: Untyped] extends Tree[T] { - type ThisType[-U] = Ident[U] + type ThisType[-U >: Untyped] = Ident[U] } val id = new Ident[Integer] diff --git a/tests/pos-scala2/rewrites.scala b/tests/pos-scala2/rewrites.scala new file mode 100644 index 000000000..3987821f1 --- /dev/null +++ b/tests/pos-scala2/rewrites.scala @@ -0,0 +1,36 @@ +trait Test { + + def baz() {} + + def bar() + + def foo() { + println("hi") + } + + lazy val x: Int +} + +object Test { + + lazy val x = 1 + + @deprecated lazy val y = 2 + + @deprecated private lazy val z = 2 + + lazy val (x1, y1) = (1, 2) + + @deprecated private lazy val (x2, y2) = (1, 2) + + val yy = x1 _ + val zz: () => Int = yy + +} + +class Stream[+A] { + + class Inner(x: A) extends Stream[A] + +} + diff --git a/tests/pos/hk.scala b/tests/pos/hk.scala index 9fdaf94f6..a8f2aa597 100644 --- a/tests/pos/hk.scala +++ b/tests/pos/hk.scala @@ -31,7 +31,7 @@ object higherKinded { } class Ident[-T >: Untyped] extends Tree[T] { - type ThisType[-U] = Ident[U] + type ThisType[-U >: Untyped] = Ident[U] } val id = new Ident[Integer] diff --git a/tests/pos/overloaded.scala b/tests/pos/overloaded.scala index a26b9b859..6a8e72714 100644 --- a/tests/pos/overloaded.scala +++ b/tests/pos/overloaded.scala @@ -21,4 +21,30 @@ object overloaded { val xs = List("a", "b") xs.mkString + + def map(f: Char => Char): String = ??? + def map[U](f: Char => U): Seq[U] = ??? + val r1 = map(x => x.toUpper) + val t1: String = r1 + val r2 = map(x => x.toInt) + val t2: Seq[Int] = r2 + + def flatMap(f: Char => String): String = ??? + def flatMap[U](f: Char => Seq[U]): Seq[U] = ??? + val r3 = flatMap(x => x.toString) + val t3: String = r3 + val r4 = flatMap(x => List(x)) + val t4: Seq[Char] = r4 + + def bar(f: (Char, Char) => Unit): Unit = ??? + def bar(f: Char => Unit) = ??? + bar((x, y) => ()) + bar (x => ()) + + def combine(f: (Char, Int) => Int): Int = ??? + def combine(f: (String, Int) => String): String = ??? + val r5 = combine((x: Char, y) => x + y) + val t5: Int = r5 + val r6 = combine((x: String, y) => x ++ y.toString) + val t6: String = r6 } diff --git a/tests/run/CollectionTests.scala b/tests/run/CollectionTests.scala index bcef54761..4696774d1 100644 --- a/tests/run/CollectionTests.scala +++ b/tests/run/CollectionTests.scala @@ -3,7 +3,7 @@ import scala.reflect.ClassTag object Test { import strawman.collections._ - import CollectionStrawMan1._ + import CollectionStrawMan4._ def seqOps(xs: Seq[Int]) = { val x1 = xs.foldLeft("")(_ + _) @@ -120,15 +120,15 @@ object Test { val ys7: String = xs7 val xs8 = xs.drop(2) val ys8: String = xs8 - val xs9 = xs.map((_: Char) + 1) // !!! need a language change to make this work without the : Char + val xs9 = xs.map(_ + 1) // !!! need a language change to make this work without the : Char val ys9: Seq[Int] = xs9 - val xs9a = xs.map((_: Char).toUpper) // !!! need a language change to make this work without the : Char + val xs9a = xs.map(_.toUpper) // !!! need a language change to make this work without the : Char val ys9a: String = xs9a val xs10 = xs.flatMap((x: Char) => s"$x,$x") val ys10: String = xs10 val xs11 = xs ++ xs val ys11: String = xs11 - val xs11a = xs ++ List('x', 'y') + val xs11a = xs ++ List('x', 'y') // Cons('x', Cons('y', Nil)) val ys11a: String = xs11a val xs12 = xs ++ Nil val ys12: String = xs12 diff --git a/tests/run/i1144/AB_1.scala b/tests/run/i1144/AB_1.scala new file mode 100644 index 000000000..ff37674db --- /dev/null +++ b/tests/run/i1144/AB_1.scala @@ -0,0 +1,6 @@ +trait A { + def x = 3 +} +trait B extends A { + override def x = super.x * 2 +} diff --git a/tests/run/i1144/C_2.scala b/tests/run/i1144/C_2.scala new file mode 100644 index 000000000..855bcec96 --- /dev/null +++ b/tests/run/i1144/C_2.scala @@ -0,0 +1,3 @@ +object Test extends B { + def main(args: Array[String]) = assert(x == 6, x) +} |