diff options
Diffstat (limited to 'compiler/src/dotty/tools/dotc/typer')
20 files changed, 10462 insertions, 0 deletions
diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala new file mode 100644 index 000000000..6c398cd72 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -0,0 +1,1351 @@ +package dotty.tools +package dotc +package typer + +import core._ +import ast.{Trees, untpd, tpd, TreeInfo} +import util.Positions._ +import util.Stats.track +import Trees.Untyped +import Mode.ImplicitsEnabled +import Contexts._ +import Flags._ +import Denotations._ +import NameOps._ +import Symbols._ +import Types._ +import Decorators._ +import ErrorReporting._ +import Trees._ +import config.Config +import Names._ +import StdNames._ +import ProtoTypes._ +import EtaExpansion._ +import Inferencing._ +import collection.mutable +import config.Printers.{typr, unapp, overload} +import TypeApplications._ +import language.implicitConversions +import reporting.diagnostic.Message + +object Applications { + import tpd._ + + def extractorMemberType(tp: Type, name: Name, errorPos: Position = NoPosition)(implicit ctx: Context) = { + val ref = tp.member(name).suchThat(_.info.isParameterless) + if (ref.isOverloaded) + errorType(i"Overloaded reference to $ref is not allowed in extractor", errorPos) + else if (ref.info.isInstanceOf[PolyType]) + errorType(i"Reference to polymorphic $ref: ${ref.info} is not allowed in extractor", errorPos) + else + ref.info.widenExpr.dealias + } + + def productSelectorTypes(tp: Type, errorPos: Position = NoPosition)(implicit ctx: Context): List[Type] = { + val sels = for (n <- Iterator.from(0)) yield extractorMemberType(tp, nme.selectorName(n), errorPos) + sels.takeWhile(_.exists).toList + } + + def productSelectors(tp: Type)(implicit ctx: Context): List[Symbol] = { + val sels = for (n <- Iterator.from(0)) yield tp.member(nme.selectorName(n)).symbol + sels.takeWhile(_.exists).toList + } + + def getUnapplySelectors(tp: Type, args: List[untpd.Tree], pos: Position = NoPosition)(implicit ctx: Context): List[Type] = + if (args.length > 1 && !(tp.derivesFrom(defn.SeqClass))) { + val sels = productSelectorTypes(tp, pos) + if (sels.length == args.length) sels + else tp :: Nil + } else tp :: Nil + + def unapplyArgs(unapplyResult: Type, unapplyFn: Tree, args: List[untpd.Tree], pos: Position = NoPosition)(implicit ctx: Context): List[Type] = { + + def seqSelector = defn.RepeatedParamType.appliedTo(unapplyResult.elemType :: Nil) + def getTp = extractorMemberType(unapplyResult, nme.get, pos) + + // println(s"unapply $unapplyResult ${extractorMemberType(unapplyResult, nme.isDefined)}") + if (extractorMemberType(unapplyResult, nme.isDefined, pos) isRef defn.BooleanClass) { + if (getTp.exists) + if (unapplyFn.symbol.name == nme.unapplySeq) { + val seqArg = boundsToHi(getTp.elemType) + if (seqArg.exists) return args map Function.const(seqArg) + } + else return getUnapplySelectors(getTp, args, pos) + else if (defn.isProductSubType(unapplyResult)) return productSelectorTypes(unapplyResult, pos) + } + if (unapplyResult derivesFrom defn.SeqClass) seqSelector :: Nil + else if (unapplyResult isRef defn.BooleanClass) Nil + else { + ctx.error(i"$unapplyResult is not a valid result type of an unapply method of an extractor", pos) + Nil + } + } + + def wrapDefs(defs: mutable.ListBuffer[Tree], tree: Tree)(implicit ctx: Context): Tree = + if (defs != null && defs.nonEmpty) tpd.Block(defs.toList, tree) else tree +} + +import Applications._ + +trait Applications extends Compatibility { self: Typer with Dynamic => + + import Applications._ + import tpd.{ cpy => _, _ } + import untpd.cpy + import Dynamic.isDynamicMethod + + /** @tparam Arg the type of arguments, could be tpd.Tree, untpd.Tree, or Type + * @param methRef the reference to the method of the application + * @param funType the type of the function part of the application + * @param args the arguments of the application + * @param resultType the expected result type of the application + */ + abstract class Application[Arg](methRef: TermRef, funType: Type, args: List[Arg], resultType: Type)(implicit ctx: Context) { + + /** The type of typed arguments: either tpd.Tree or Type */ + type TypedArg + + /** Given an original argument and the type of the corresponding formal + * parameter, produce a typed argument. + */ + protected def typedArg(arg: Arg, formal: Type): TypedArg + + /** Turn a typed tree into an argument */ + protected def treeToArg(arg: Tree): Arg + + /** Check that argument corresponds to type `formal` and + * possibly add it to the list of adapted arguments + */ + protected def addArg(arg: TypedArg, formal: Type): Unit + + /** Is this an argument of the form `expr: _*` or a RepeatedParamType + * derived from such an argument? + */ + protected def isVarArg(arg: Arg): Boolean + + /** If constructing trees, turn last `n` processed arguments into a + * `SeqLiteral` tree with element type `elemFormal`. + */ + protected def makeVarArg(n: Int, elemFormal: Type): Unit + + /** If all `args` have primitive numeric types, make sure it's the same one */ + protected def harmonizeArgs(args: List[TypedArg]): List[TypedArg] + + /** Signal failure with given message at position of given argument */ + protected def fail(msg: => Message, arg: Arg): Unit + + /** Signal failure with given message at position of the application itself */ + protected def fail(msg: => Message): Unit + + protected def appPos: Position + + /** The current function part, which might be affected by lifting. + */ + protected def normalizedFun: Tree + + /** If constructing trees, pull out all parts of the function + * which are not idempotent into separate prefix definitions + */ + protected def liftFun(): Unit = () + + /** A flag signalling that the typechecking the application was so far successful */ + private[this] var _ok = true + + def ok = _ok + def ok_=(x: Boolean) = { + assert(x || ctx.reporter.errorsReported || !ctx.typerState.isCommittable) // !!! DEBUG + _ok = x + } + + /** The function's type after widening and instantiating polytypes + * with polyparams in constraint set + */ + val methType = funType.widen match { + case funType: MethodType => funType + case funType: PolyType => constrained(funType).resultType + case tp => tp //was: funType + } + + /** The arguments re-ordered so that each named argument matches the + * same-named formal parameter. + */ + lazy val orderedArgs = + if (hasNamedArg(args)) + reorder(args.asInstanceOf[List[untpd.Tree]]).asInstanceOf[List[Arg]] + else + args + + protected def init() = methType match { + case methType: MethodType => + // apply the result type constraint, unless method type is dependent + if (!methType.isDependent) { + val savedConstraint = ctx.typerState.constraint + if (!constrainResult(methType.resultType, resultType)) + if (ctx.typerState.isCommittable) + // defer the problem until after the application; + // it might be healed by an implicit conversion + assert(ctx.typerState.constraint eq savedConstraint) + else + fail(err.typeMismatchMsg(methType.resultType, resultType)) + } + // match all arguments with corresponding formal parameters + matchArgs(orderedArgs, methType.paramTypes, 0) + case _ => + if (methType.isError) ok = false + else fail(s"$methString does not take parameters") + } + + /** The application was successful */ + def success = ok + + protected def methodType = methType.asInstanceOf[MethodType] + private def methString: String = i"${methRef.symbol}: ${methType.show}" + + /** Re-order arguments to correctly align named arguments */ + def reorder[T >: Untyped](args: List[Trees.Tree[T]]): List[Trees.Tree[T]] = { + + /** @param pnames The list of parameter names that are missing arguments + * @param args The list of arguments that are not yet passed, or that are waiting to be dropped + * @param nameToArg A map from as yet unseen names to named arguments + * @param toDrop A set of names that have already be passed as named arguments + * + * For a well-typed application we have the invariants + * + * 1. `(args diff toDrop)` can be reordered to match `pnames` + * 2. For every `(name -> arg)` in `nameToArg`, `arg` is an element of `args` + */ + def recur(pnames: List[Name], args: List[Trees.Tree[T]], + nameToArg: Map[Name, Trees.NamedArg[T]], toDrop: Set[Name]): List[Trees.Tree[T]] = pnames match { + case pname :: pnames1 if nameToArg contains pname => + // there is a named argument for this parameter; pick it + nameToArg(pname) :: recur(pnames1, args, nameToArg - pname, toDrop + pname) + case _ => + def pnamesRest = if (pnames.isEmpty) pnames else pnames.tail + args match { + case (arg @ NamedArg(aname, _)) :: args1 => + if (toDrop contains aname) // argument is already passed + recur(pnames, args1, nameToArg, toDrop - aname) + else if ((nameToArg contains aname) && pnames.nonEmpty) // argument is missing, pass an empty tree + genericEmptyTree :: recur(pnames.tail, args, nameToArg, toDrop) + else { // name not (or no longer) available for named arg + def msg = + if (methodType.paramNames contains aname) + s"parameter $aname of $methString is already instantiated" + else + s"$methString does not have a parameter $aname" + fail(msg, arg.asInstanceOf[Arg]) + arg :: recur(pnamesRest, args1, nameToArg, toDrop) + } + case arg :: args1 => + arg :: recur(pnamesRest, args1, nameToArg, toDrop) // unnamed argument; pick it + case Nil => // no more args, continue to pick up any preceding named args + if (pnames.isEmpty) Nil + else recur(pnamesRest, args, nameToArg, toDrop) + } + } + val nameAssocs = for (arg @ NamedArg(name, _) <- args) yield (name, arg) + recur(methodType.paramNames, args, nameAssocs.toMap, Set()) + } + + /** Splice new method reference into existing application */ + def spliceMeth(meth: Tree, app: Tree): Tree = app match { + case Apply(fn, args) => Apply(spliceMeth(meth, fn), args) + case TypeApply(fn, targs) => TypeApply(spliceMeth(meth, fn), targs) + case _ => meth + } + + /** Find reference to default parameter getter for parameter #n in current + * parameter list, or NoType if none was found + */ + def findDefaultGetter(n: Int)(implicit ctx: Context): Tree = { + val meth = methRef.symbol.asTerm + val receiver: Tree = methPart(normalizedFun) match { + case Select(receiver, _) => receiver + case mr => mr.tpe.normalizedPrefix match { + case mr: TermRef => ref(mr) + case mr => + if (this.isInstanceOf[TestApplication[_]]) + // In this case it is safe to skolemize now; we will produce a stable prefix for the actual call. + ref(mr.narrow) + else + EmptyTree + } + } + val getterPrefix = + if ((meth is Synthetic) && meth.name == nme.apply) nme.CONSTRUCTOR else meth.name + def getterName = getterPrefix.defaultGetterName(n) + if (!meth.hasDefaultParams) + EmptyTree + else if (receiver.isEmpty) { + def findGetter(cx: Context): Tree = { + if (cx eq NoContext) EmptyTree + else if (cx.scope != cx.outer.scope && + cx.denotNamed(meth.name).hasAltWith(_.symbol == meth)) { + val denot = cx.denotNamed(getterName) + assert(denot.exists, s"non-existent getter denotation ($denot) for getter($getterName)") + ref(TermRef(cx.owner.thisType, getterName, denot)) + } else findGetter(cx.outer) + } + findGetter(ctx) + } + else { + def selectGetter(qual: Tree): Tree = { + val getterDenot = qual.tpe.member(getterName) + if (getterDenot.exists) qual.select(TermRef(qual.tpe, getterName, getterDenot)) + else EmptyTree + } + if (!meth.isClassConstructor) + selectGetter(receiver) + else { + // default getters for class constructors are found in the companion object + val cls = meth.owner + val companion = cls.companionModule + receiver.tpe.baseTypeRef(cls) match { + case tp: TypeRef if companion.isTerm => + selectGetter(ref(TermRef(tp.prefix, companion.asTerm))) + case _ => + EmptyTree + } + } + } + } + + /** Match re-ordered arguments against formal parameters + * @param n The position of the first parameter in formals in `methType`. + */ + def matchArgs(args: List[Arg], formals: List[Type], n: Int): Unit = { + if (success) formals match { + case formal :: formals1 => + + def addTyped(arg: Arg, formal: Type) = + addArg(typedArg(arg, formal), formal) + + def missingArg(n: Int): Unit = { + val pname = methodType.paramNames(n) + fail( + if (pname contains '$') s"not enough arguments for $methString" + else s"missing argument for parameter $pname of $methString") + } + + def tryDefault(n: Int, args1: List[Arg]): Unit = { + liftFun() + val getter = findDefaultGetter(n + numArgs(normalizedFun)) + if (getter.isEmpty) missingArg(n) + else { + addTyped(treeToArg(spliceMeth(getter withPos appPos, normalizedFun)), formal) + matchArgs(args1, formals1, n + 1) + } + } + + if (formal.isRepeatedParam) + args match { + case arg :: Nil if isVarArg(arg) => + addTyped(arg, formal) + case _ => + val elemFormal = formal.widenExpr.argTypesLo.head + val origConstraint = ctx.typerState.constraint + var typedArgs = args.map(typedArg(_, elemFormal)) + val harmonizedArgs = harmonizeArgs(typedArgs) + if (harmonizedArgs ne typedArgs) { + ctx.typerState.constraint = origConstraint + typedArgs = harmonizedArgs + } + typedArgs.foreach(addArg(_, elemFormal)) + makeVarArg(args.length, elemFormal) + } + else args match { + case EmptyTree :: args1 => + tryDefault(n, args1) + case arg :: args1 => + addTyped(arg, formal) + matchArgs(args1, formals1, n + 1) + case nil => + tryDefault(n, args) + } + + case nil => + args match { + case arg :: args1 => fail(s"too many arguments for $methString", arg) + case nil => + } + } + } + } + + /** Subclass of Application for the cases where we are interested only + * in a "can/cannot apply" answer, without needing to construct trees or + * issue error messages. + */ + abstract class TestApplication[Arg](methRef: TermRef, funType: Type, args: List[Arg], resultType: Type)(implicit ctx: Context) + extends Application[Arg](methRef, funType, args, resultType) { + type TypedArg = Arg + type Result = Unit + + /** The type of the given argument */ + protected def argType(arg: Arg, formal: Type): Type + + def typedArg(arg: Arg, formal: Type): Arg = arg + def addArg(arg: TypedArg, formal: Type) = + ok = ok & isCompatible(argType(arg, formal), formal) + def makeVarArg(n: Int, elemFormal: Type) = {} + def fail(msg: => Message, arg: Arg) = + ok = false + def fail(msg: => Message) = + ok = false + def appPos = NoPosition + lazy val normalizedFun = ref(methRef) + init() + } + + /** Subclass of Application for applicability tests with type arguments and value + * argument trees. + */ + class ApplicableToTrees(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context) + extends TestApplication(methRef, methRef.widen.appliedTo(targs), args, resultType) { + def argType(arg: Tree, formal: Type): Type = normalize(arg.tpe, formal) + def treeToArg(arg: Tree): Tree = arg + def isVarArg(arg: Tree): Boolean = tpd.isWildcardStarArg(arg) + def harmonizeArgs(args: List[Tree]) = harmonize(args) + } + + /** Subclass of Application for applicability tests with type arguments and value + * argument trees. + */ + class ApplicableToTreesDirectly(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context) extends ApplicableToTrees(methRef, targs, args, resultType)(ctx) { + override def addArg(arg: TypedArg, formal: Type) = + ok = ok & (argType(arg, formal) <:< formal) + } + + /** Subclass of Application for applicability tests with value argument types. */ + class ApplicableToTypes(methRef: TermRef, args: List[Type], resultType: Type)(implicit ctx: Context) + extends TestApplication(methRef, methRef, args, resultType) { + def argType(arg: Type, formal: Type): Type = arg + def treeToArg(arg: Tree): Type = arg.tpe + def isVarArg(arg: Type): Boolean = arg.isRepeatedParam + def harmonizeArgs(args: List[Type]) = harmonizeTypes(args) + } + + /** Subclass of Application for type checking an Apply node, where + * types of arguments are either known or unknown. + */ + abstract class TypedApply[T >: Untyped]( + app: untpd.Apply, fun: Tree, methRef: TermRef, args: List[Trees.Tree[T]], resultType: Type)(implicit ctx: Context) + extends Application(methRef, fun.tpe, args, resultType) { + type TypedArg = Tree + def isVarArg(arg: Trees.Tree[T]): Boolean = untpd.isWildcardStarArg(arg) + private var typedArgBuf = new mutable.ListBuffer[Tree] + private var liftedDefs: mutable.ListBuffer[Tree] = null + private var myNormalizedFun: Tree = fun + init() + + def addArg(arg: Tree, formal: Type): Unit = + typedArgBuf += adaptInterpolated(arg, formal.widenExpr, EmptyTree) + + def makeVarArg(n: Int, elemFormal: Type): Unit = { + val args = typedArgBuf.takeRight(n).toList + typedArgBuf.trimEnd(n) + val elemtpt = TypeTree(elemFormal) + val seqLit = + if (methodType.isJava) JavaSeqLiteral(args, elemtpt) + else SeqLiteral(args, elemtpt) + typedArgBuf += seqToRepeated(seqLit) + } + + def harmonizeArgs(args: List[TypedArg]) = harmonize(args) + + override def appPos = app.pos + + def fail(msg: => Message, arg: Trees.Tree[T]) = { + ctx.error(msg, arg.pos) + ok = false + } + + def fail(msg: => Message) = { + ctx.error(msg, app.pos) + ok = false + } + + def normalizedFun = myNormalizedFun + + override def liftFun(): Unit = + if (liftedDefs == null) { + liftedDefs = new mutable.ListBuffer[Tree] + myNormalizedFun = liftApp(liftedDefs, myNormalizedFun) + } + + /** The index of the first difference between lists of trees `xs` and `ys`, + * where `EmptyTree`s in the second list are skipped. + * -1 if there are no differences. + */ + private def firstDiff[T <: Trees.Tree[_]](xs: List[T], ys: List[T], n: Int = 0): Int = xs match { + case x :: xs1 => + ys match { + case EmptyTree :: ys1 => firstDiff(xs1, ys1, n) + case y :: ys1 => if (x ne y) n else firstDiff(xs1, ys1, n + 1) + case nil => n + } + case nil => + ys match { + case EmptyTree :: ys1 => firstDiff(xs, ys1, n) + case y :: ys1 => n + case nil => -1 + } + } + private def sameSeq[T <: Trees.Tree[_]](xs: List[T], ys: List[T]): Boolean = firstDiff(xs, ys) < 0 + + val result = { + var typedArgs = typedArgBuf.toList + def app0 = cpy.Apply(app)(normalizedFun, typedArgs) // needs to be a `def` because typedArgs can change later + val app1 = + if (!success) app0.withType(ErrorType) + else { + if (!sameSeq(args, orderedArgs)) { + // need to lift arguments to maintain evaluation order in the + // presence of argument reorderings. + liftFun() + val eqSuffixLength = firstDiff(app.args.reverse, orderedArgs.reverse) + val (liftable, rest) = typedArgs splitAt (typedArgs.length - eqSuffixLength) + typedArgs = liftArgs(liftedDefs, methType, liftable) ++ rest + } + if (sameSeq(typedArgs, args)) // trick to cut down on tree copying + typedArgs = args.asInstanceOf[List[Tree]] + assignType(app0, normalizedFun, typedArgs) + } + wrapDefs(liftedDefs, app1) + } + } + + /** Subclass of Application for type checking an Apply node with untyped arguments. */ + class ApplyToUntyped(app: untpd.Apply, fun: Tree, methRef: TermRef, proto: FunProto, resultType: Type)(implicit ctx: Context) + extends TypedApply(app, fun, methRef, proto.args, resultType) { + def typedArg(arg: untpd.Tree, formal: Type): TypedArg = proto.typedArg(arg, formal.widenExpr) + def treeToArg(arg: Tree): untpd.Tree = untpd.TypedSplice(arg) + } + + /** Subclass of Application for type checking an Apply node with typed arguments. */ + class ApplyToTyped(app: untpd.Apply, fun: Tree, methRef: TermRef, args: List[Tree], resultType: Type)(implicit ctx: Context) + extends TypedApply[Type](app, fun, methRef, args, resultType) { + // Dotty deviation: Dotc infers Untyped for the supercall. This seems to be according to the rules + // (of both Scala and Dotty). Untyped is legal, and a subtype of Typed, whereas TypeApply + // is invariant in the type parameter, so the minimal type should be inferred. But then typedArg does + // not match the abstract method in Application and an abstract class error results. + def typedArg(arg: tpd.Tree, formal: Type): TypedArg = arg + def treeToArg(arg: Tree): Tree = arg + } + + /** If `app` is a `this(...)` constructor call, the this-call argument context, + * otherwise the current context. + */ + def argCtx(app: untpd.Tree)(implicit ctx: Context): Context = + if (untpd.isSelfConstrCall(app)) ctx.thisCallArgContext else ctx + + def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = { + + def realApply(implicit ctx: Context): Tree = track("realApply") { + val originalProto = new FunProto(tree.args, IgnoredProto(pt), this)(argCtx(tree)) + val fun1 = typedExpr(tree.fun, originalProto) + + // Warning: The following lines are dirty and fragile. We record that auto-tupling was demanded as + // a side effect in adapt. If it was, we assume the tupled proto-type in the rest of the application, + // until, possibly, we have to fall back to insert an implicit on the qualifier. + // This crucially relies on he fact that `proto` is used only in a single call of `adapt`, + // otherwise we would get possible cross-talk between different `adapt` calls using the same + // prototype. A cleaner alternative would be to return a modified prototype from `adapt` together with + // a modified tree but this would be more convoluted and less efficient. + val proto = if (originalProto.isTupled) originalProto.tupled else originalProto + + // If some of the application's arguments are function literals without explicitly declared + // parameter types, relate the normalized result type of the application with the + // expected type through `constrainResult`. This can add more constraints which + // help sharpen the inferred parameter types for the argument function literal(s). + // This tweak is needed to make i1378 compile. + if (tree.args.exists(untpd.isFunctionWithUnknownParamType(_))) + if (!constrainResult(fun1.tpe.widen, proto.derivedFunProto(resultType = pt))) + typr.println(i"result failure for $tree with type ${fun1.tpe.widen}, expected = $pt") + + /** Type application where arguments come from prototype, and no implicits are inserted */ + def simpleApply(fun1: Tree, proto: FunProto)(implicit ctx: Context): Tree = + methPart(fun1).tpe match { + case funRef: TermRef => + val app = + if (proto.allArgTypesAreCurrent()) + new ApplyToTyped(tree, fun1, funRef, proto.typedArgs, pt) + else + new ApplyToUntyped(tree, fun1, funRef, proto, pt)(argCtx(tree)) + convertNewGenericArray(ConstFold(app.result)) + case _ => + handleUnexpectedFunType(tree, fun1) + } + + /** Try same application with an implicit inserted around the qualifier of the function + * part. Return an optional value to indicate success. + */ + def tryWithImplicitOnQualifier(fun1: Tree, proto: FunProto)(implicit ctx: Context): Option[Tree] = + tryInsertImplicitOnQualifier(fun1, proto) flatMap { fun2 => + tryEither { + implicit ctx => Some(simpleApply(fun2, proto)): Option[Tree] + } { + (_, _) => None + } + } + + fun1.tpe match { + case ErrorType => untpd.cpy.Apply(tree)(fun1, tree.args).withType(ErrorType) + case TryDynamicCallType => typedDynamicApply(tree, pt) + case _ => + tryEither { + implicit ctx => simpleApply(fun1, proto) + } { + (failedVal, failedState) => + def fail = { failedState.commit(); failedVal } + // Try once with original prototype and once (if different) with tupled one. + // The reason we need to try both is that the decision whether to use tupled + // or not was already taken but might have to be revised when an implicit + // is inserted on the qualifier. + tryWithImplicitOnQualifier(fun1, originalProto).getOrElse( + if (proto eq originalProto) fail + else tryWithImplicitOnQualifier(fun1, proto).getOrElse(fail)) + } + } + } + + /** Convert expression like + * + * e += (args) + * + * where the lifted-for-assignment version of e is { val xs = es; e' } to + * + * { val xs = es; e' = e' + args } + */ + def typedOpAssign: Tree = track("typedOpAssign") { + val Apply(Select(lhs, name), rhss) = tree + val lhs1 = typedExpr(lhs) + val liftedDefs = new mutable.ListBuffer[Tree] + val lhs2 = untpd.TypedSplice(liftAssigned(liftedDefs, lhs1)) + val assign = untpd.Assign(lhs2, untpd.Apply(untpd.Select(lhs2, name.init), rhss)) + wrapDefs(liftedDefs, typed(assign)) + } + + if (untpd.isOpAssign(tree)) + tryEither { + implicit ctx => realApply + } { (failedVal, failedState) => + tryEither { + implicit ctx => typedOpAssign + } { (_, _) => + failedState.commit() + failedVal + } + } + else { + val app = realApply + app match { + case Apply(fn @ Select(left, _), right :: Nil) if fn.hasType => + val op = fn.symbol + if (op == defn.Any_== || op == defn.Any_!=) + checkCanEqual(left.tpe.widen, right.tpe.widen, app.pos) + case _ => + } + app + } + } + + /** Overridden in ReTyper to handle primitive operations that can be generated after erasure */ + protected def handleUnexpectedFunType(tree: untpd.Apply, fun: Tree)(implicit ctx: Context): Tree = + throw new Error(i"unexpected type.\n fun = $fun,\n methPart(fun) = ${methPart(fun)},\n methPart(fun).tpe = ${methPart(fun).tpe},\n tpe = ${fun.tpe}") + + def typedNamedArgs(args: List[untpd.Tree])(implicit ctx: Context) = + for (arg @ NamedArg(id, argtpt) <- args) yield { + val argtpt1 = typedType(argtpt) + cpy.NamedArg(arg)(id, argtpt1).withType(argtpt1.tpe) + } + + def typedTypeApply(tree: untpd.TypeApply, pt: Type)(implicit ctx: Context): Tree = track("typedTypeApply") { + val isNamed = hasNamedArg(tree.args) + val typedArgs = if (isNamed) typedNamedArgs(tree.args) else tree.args.mapconserve(typedType(_)) + val typedFn = typedExpr(tree.fun, PolyProto(typedArgs.tpes, pt)) + typedFn.tpe.widen match { + case pt: PolyType => + if (typedArgs.length <= pt.paramBounds.length && !isNamed) + if (typedFn.symbol == defn.Predef_classOf && typedArgs.nonEmpty) { + val arg = typedArgs.head + checkClassType(arg.tpe, arg.pos, traitReq = false, stablePrefixReq = false) + } + case _ => + } + def tryDynamicTypeApply(): Tree = typedFn match { + case typedFn: Select if !pt.isInstanceOf[FunProto] => typedDynamicSelect(typedFn, typedArgs, pt) + case _ => tree.withType(TryDynamicCallType) + } + if (typedFn.tpe eq TryDynamicCallType) tryDynamicTypeApply() + else assignType(cpy.TypeApply(tree)(typedFn, typedArgs), typedFn, typedArgs) + } + + /** Rewrite `new Array[T](....)` if T is an unbounded generic to calls to newGenericArray. + * It is performed during typer as creation of generic arrays needs a classTag. + * we rely on implicit search to find one. + */ + def convertNewGenericArray(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match { + case Apply(TypeApply(tycon, targs@(targ :: Nil)), args) if tycon.symbol == defn.ArrayConstructor => + fullyDefinedType(tree.tpe, "array", tree.pos) + + def newGenericArrayCall = + ref(defn.DottyArraysModule) + .select(defn.newGenericArrayMethod).withPos(tree.pos) + .appliedToTypeTrees(targs).appliedToArgs(args) + + if (TypeErasure.isUnboundedGeneric(targ.tpe)) + newGenericArrayCall + else tree + case _ => + tree + } + + def typedUnApply(tree: untpd.Apply, selType: Type)(implicit ctx: Context): Tree = track("typedUnApply") { + val Apply(qual, args) = tree + + def notAnExtractor(tree: Tree) = + errorTree(tree, s"${qual.show} cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method") + + /** If this is a term ref tree, try to typecheck with its type name. + * If this refers to a type alias, follow the alias, and if + * one finds a class, reference the class companion module. + */ + def followTypeAlias(tree: untpd.Tree): untpd.Tree = { + tree match { + case tree: untpd.RefTree => + val ttree = typedType(untpd.rename(tree, tree.name.toTypeName)) + ttree.tpe match { + case alias: TypeRef if alias.info.isAlias => + companionRef(alias) match { + case companion: TermRef => return untpd.ref(companion) withPos tree.pos + case _ => + } + case _ => + } + case _ => + } + untpd.EmptyTree + } + + /** A typed qual.unapply or qual.unapplySeq tree, if this typechecks. + * Otherwise fallBack with (maltyped) qual.unapply as argument + * Note: requires special handling for overloaded occurrences of + * unapply or unapplySeq. We first try to find a non-overloaded + * method which matches any type. If that fails, we try to find an + * overloaded variant which matches one of the argument types. + * In fact, overloaded unapply's are problematic because a non- + * overloaded unapply does *not* need to be applicable to its argument + * whereas overloaded variants need to have a conforming variant. + */ + def trySelectUnapply(qual: untpd.Tree)(fallBack: Tree => Tree): Tree = { + val genericProto = new UnapplyFunProto(WildcardType, this) + def specificProto = new UnapplyFunProto(selType, this) + // try first for non-overloaded, then for overloaded ocurrences + def tryWithName(name: TermName)(fallBack: Tree => Tree)(implicit ctx: Context): Tree = + tryEither { + implicit ctx => typedExpr(untpd.Select(qual, name), specificProto) + } { + (sel, _) => + tryEither { + implicit ctx => typedExpr(untpd.Select(qual, name), genericProto) + } { + (_, _) => fallBack(sel) + } + } + // try first for unapply, then for unapplySeq + tryWithName(nme.unapply) { + sel => tryWithName(nme.unapplySeq)(_ => fallBack(sel)) // for backwards compatibility; will be dropped + } + } + + /** Produce a typed qual.unapply or qual.unapplySeq tree, or + * else if this fails follow a type alias and try again. + */ + val unapplyFn = trySelectUnapply(qual) { sel => + val qual1 = followTypeAlias(qual) + if (qual1.isEmpty) notAnExtractor(sel) + else trySelectUnapply(qual1)(_ => notAnExtractor(sel)) + } + + def fromScala2x = unapplyFn.symbol.exists && (unapplyFn.symbol.owner is Scala2x) + + /** Is `subtp` a subtype of `tp` or of some generalization of `tp`? + * The generalizations of a type T are the smallest set G such that + * + * - T is in G + * - If a typeref R in G represents a class or trait, R's superclass is in G. + * - If a type proxy P is not a reference to a class, P's supertype is in G + */ + def isSubTypeOfParent(subtp: Type, tp: Type)(implicit ctx: Context): Boolean = + if (subtp <:< tp) true + else tp match { + case tp: TypeRef if tp.symbol.isClass => isSubTypeOfParent(subtp, tp.firstParent) + case tp: TypeProxy => isSubTypeOfParent(subtp, tp.superType) + case _ => false + } + + unapplyFn.tpe.widen match { + case mt: MethodType if mt.paramTypes.length == 1 => + val unapplyArgType = mt.paramTypes.head + unapp.println(i"unapp arg tpe = $unapplyArgType, pt = $selType") + val ownType = + if (selType <:< unapplyArgType) { + unapp.println(i"case 1 $unapplyArgType ${ctx.typerState.constraint}") + selType + } else if (isSubTypeOfParent(unapplyArgType, selType)(ctx.addMode(Mode.GADTflexible))) { + maximizeType(unapplyArgType) match { + case Some(tvar) => + def msg = + ex"""There is no best instantiation of pattern type $unapplyArgType + |that makes it a subtype of selector type $selType. + |Non-variant type variable ${tvar.origin} cannot be uniquely instantiated.""" + if (fromScala2x) { + // We can't issue an error here, because in Scala 2, ::[B] is invariant + // whereas List[+T] is covariant. According to the strict rule, a pattern + // match of a List[C] against a case x :: xs is illegal, because + // B cannot be uniquely instantiated. Of course :: should have been + // covariant in the first place, but in the Scala libraries it isn't. + // So for now we allow these kinds of patterns, even though they + // can open unsoundness holes. See SI-7952 for an example of the hole this opens. + if (ctx.settings.verbose.value) ctx.warning(msg, tree.pos) + } else { + unapp.println(s" ${unapplyFn.symbol.owner} ${unapplyFn.symbol.owner is Scala2x}") + ctx.strictWarning(msg, tree.pos) + } + case _ => + } + unapp.println(i"case 2 $unapplyArgType ${ctx.typerState.constraint}") + unapplyArgType + } else { + unapp.println("Neither sub nor super") + unapp.println(TypeComparer.explained(implicit ctx => unapplyArgType <:< selType)) + errorType( + ex"Pattern type $unapplyArgType is neither a subtype nor a supertype of selector type $selType", + tree.pos) + } + + val dummyArg = dummyTreeOfType(ownType) + val unapplyApp = typedExpr(untpd.TypedSplice(Apply(unapplyFn, dummyArg :: Nil))) + val unapplyImplicits = unapplyApp match { + case Apply(Apply(unapply, `dummyArg` :: Nil), args2) => assert(args2.nonEmpty); args2 + case Apply(unapply, `dummyArg` :: Nil) => Nil + } + + var argTypes = unapplyArgs(unapplyApp.tpe, unapplyFn, args, tree.pos) + for (argType <- argTypes) assert(!argType.isInstanceOf[TypeBounds], unapplyApp.tpe.show) + val bunchedArgs = argTypes match { + case argType :: 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 + } + if (argTypes.length != bunchedArgs.length) { + ctx.error(em"wrong number of argument patterns for $qual; expected: ($argTypes%, %)", tree.pos) + argTypes = argTypes.take(args.length) ++ + List.fill(argTypes.length - args.length)(WildcardType) + } + val unapplyPatterns = (bunchedArgs, argTypes).zipped map (typed(_, _)) + val result = assignType(cpy.UnApply(tree)(unapplyFn, unapplyImplicits, unapplyPatterns), ownType) + unapp.println(s"unapply patterns = $unapplyPatterns") + if ((ownType eq selType) || ownType.isError) result + else Typed(result, TypeTree(ownType)) + case tp => + val unapplyErr = if (tp.isError) unapplyFn else notAnExtractor(unapplyFn) + val typedArgsErr = args mapconserve (typed(_, defn.AnyType)) + cpy.UnApply(tree)(unapplyErr, Nil, typedArgsErr) withType ErrorType + } + } + + /** A typed unapply hook, can be overridden by re any-typers between frontend + * and pattern matcher. + */ + def typedUnApply(tree: untpd.UnApply, selType: Type)(implicit ctx: Context): UnApply = + throw new UnsupportedOperationException("cannot type check an UnApply node") + + /** Is given method reference applicable to type arguments `targs` and argument trees `args`? + * @param resultType The expected result type of the application + */ + def isApplicable(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean = { + val nestedContext = ctx.fresh.setExploreTyperState + new ApplicableToTrees(methRef, targs, args, resultType)(nestedContext).success + } + + /** Is given method reference applicable to type arguments `targs` and argument trees `args` without inferring views? + * @param resultType The expected result type of the application + */ + def isDirectlyApplicable(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean = { + val nestedContext = ctx.fresh.setExploreTyperState + new ApplicableToTreesDirectly(methRef, targs, args, resultType)(nestedContext).success + } + + /** Is given method reference applicable to argument types `args`? + * @param resultType The expected result type of the application + */ + def isApplicable(methRef: TermRef, args: List[Type], resultType: Type)(implicit ctx: Context): Boolean = { + val nestedContext = ctx.fresh.setExploreTyperState + new ApplicableToTypes(methRef, args, resultType)(nestedContext).success + } + + /** Is given type applicable to type arguments `targs` and argument trees `args`, + * possibly after inserting an `apply`? + * @param resultType The expected result type of the application + */ + def isApplicable(tp: Type, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean = + onMethod(tp, isApplicable(_, targs, args, resultType)) + + /** Is given type applicable to argument types `args`, possibly after inserting an `apply`? + * @param resultType The expected result type of the application + */ + def isApplicable(tp: Type, args: List[Type], resultType: Type)(implicit ctx: Context): Boolean = + onMethod(tp, isApplicable(_, args, resultType)) + + private def onMethod(tp: Type, p: TermRef => Boolean)(implicit ctx: Context): Boolean = tp match { + case methRef: TermRef if methRef.widenSingleton.isInstanceOf[MethodicType] => + p(methRef) + case mt: MethodicType => + p(mt.narrow) + case _ => + tp.member(nme.apply).hasAltWith(d => p(TermRef(tp, nme.apply, d))) + } + + /** In a set of overloaded applicable alternatives, is `alt1` at least as good as + * `alt2`? `alt1` and `alt2` are non-overloaded references. + */ + def isAsGood(alt1: TermRef, alt2: TermRef)(implicit ctx: Context): Boolean = track("isAsGood") { ctx.traceIndented(i"isAsGood($alt1, $alt2)", overload) { + + assert(alt1 ne alt2) + + /** Is class or module class `sym1` derived from class or module class `sym2`? + * Module classes also inherit the relationship from their companions. + */ + def isDerived(sym1: Symbol, sym2: Symbol): Boolean = + if (sym1 isSubClass sym2) true + else if (sym2 is Module) isDerived(sym1, sym2.companionClass) + else (sym1 is Module) && isDerived(sym1.companionClass, sym2) + + /** Is alternative `alt1` with type `tp1` as specific as alternative + * `alt2` with type `tp2` ? + * + * 1. A method `alt1` of type (p1: T1, ..., pn: Tn)U is as specific as `alt2` + * if `alt2` is applicable to arguments (p1, ..., pn) of types T1,...,Tn + * or if `alt1` is nullary. + * 2. A polymorphic member of type [a1 >: L1 <: U1, ..., an >: Ln <: Un]T is as + * specific as `alt2` of type `tp2` if T is as specific as `tp2` under the + * assumption that for i = 1,...,n each ai is an abstract type name bounded + * from below by Li and from above by Ui. + * 3. A member of any other type `tp1` is: + * a. always as specific as a method or a polymorphic method. + * b. as specific as a member of any other type `tp2` if `tp1` is compatible + * with `tp2`. + */ + def isAsSpecific(alt1: TermRef, tp1: Type, alt2: TermRef, tp2: Type): Boolean = ctx.traceIndented(i"isAsSpecific $tp1 $tp2", overload) { tp1 match { + case tp1: MethodType => // (1) + def repeatedToSingle(tp: Type): Type = tp match { + case tp @ ExprType(tp1) => tp.derivedExprType(repeatedToSingle(tp1)) + case _ => if (tp.isRepeatedParam) tp.argTypesHi.head else tp + } + val formals1 = + if (tp1.isVarArgsMethod && tp2.isVarArgsMethod) tp1.paramTypes map repeatedToSingle + else tp1.paramTypes + isApplicable(alt2, formals1, WildcardType) || + tp1.paramTypes.isEmpty && tp2.isInstanceOf[MethodOrPoly] + case tp1: PolyType => // (2) + val tparams = ctx.newTypeParams(alt1.symbol, tp1.paramNames, EmptyFlags, tp1.instantiateBounds) + isAsSpecific(alt1, tp1.instantiate(tparams map (_.typeRef)), alt2, tp2) + case _ => // (3) + tp2 match { + case tp2: MethodType => true // (3a) + case tp2: PolyType if tp2.isPolymorphicMethodType => true // (3a) + case tp2: PolyType => // (3b) + val nestedCtx = ctx.fresh.setExploreTyperState + + { + implicit val ctx: Context = nestedCtx + isAsSpecificValueType(tp1, constrained(tp2).resultType) + } + case _ => // (3b) + isAsSpecificValueType(tp1, tp2) + } + }} + + /** Test whether value type `tp1` is as specific as value type `tp2`. + * Let's abbreviate this to `tp1 <:s tp2`. + * Previously, `<:s` was the same as `<:`. This behavior is still + * available under mode `Mode.OldOverloadingResolution`. The new behavior + * is different, however. Here, `T <:s U` iff + * + * flip(T) <: flip(U) + * + * where `flip` changes top-level contravariant type aliases to covariant ones. + * Intuitively `<:s` means subtyping `<:`, except that all top-level arguments + * to contravariant parameters are compared as if they were covariant. E.g. given class + * + * class Cmp[-X] + * + * `Cmp[T] <:s Cmp[U]` if `T <: U`. On the other hand, nested occurrences + * of parameters are not affected. + * So `T <: U` would imply `List[Cmp[U]] <:s List[Cmp[T]]`, as usual. + * + * This relation might seem strange, but it models closely what happens for methods. + * Indeed, if we integrate the existing rules for methods into `<:s` we have now that + * + * (T)R <:s (U)R + * + * iff + * + * T => R <:s U => R + */ + def isAsSpecificValueType(tp1: Type, tp2: Type)(implicit ctx: Context) = + if (ctx.mode.is(Mode.OldOverloadingResolution)) + isCompatible(tp1, tp2) + else { + val flip = new TypeMap { + def apply(t: Type) = t match { + case t: TypeAlias if variance > 0 && t.variance < 0 => t.derivedTypeAlias(t.alias, 1) + case t: TypeBounds => t + case _ => mapOver(t) + } + } + isCompatible(flip(tp1), flip(tp2)) + } + + /** Drop any implicit parameter section */ + def stripImplicit(tp: Type): Type = tp match { + case mt: ImplicitMethodType if !mt.isDependent => + mt.resultType + // todo: make sure implicit method types are not dependent? + // but check test case in /tests/pos/depmet_implicit_chaining_zw.scala + case pt: PolyType => + pt.derivedPolyType(pt.paramNames, pt.paramBounds, stripImplicit(pt.resultType)) + case _ => + tp + } + + val owner1 = if (alt1.symbol.exists) alt1.symbol.owner else NoSymbol + val owner2 = if (alt2.symbol.exists) alt2.symbol.owner else NoSymbol + val tp1 = stripImplicit(alt1.widen) + val tp2 = stripImplicit(alt2.widen) + + def winsOwner1 = isDerived(owner1, owner2) + def winsType1 = isAsSpecific(alt1, tp1, alt2, tp2) + def winsOwner2 = isDerived(owner2, owner1) + def winsType2 = isAsSpecific(alt2, tp2, alt1, tp1) + + overload.println(i"isAsGood($alt1, $alt2)? $tp1 $tp2 $winsOwner1 $winsType1 $winsOwner2 $winsType2") + + // Assume the following probabilities: + // + // P(winsOwnerX) = 2/3 + // P(winsTypeX) = 1/3 + // + // Then the call probabilities of the 4 basic operations are as follows: + // + // winsOwner1: 1/1 + // winsOwner2: 1/1 + // winsType1 : 7/9 + // winsType2 : 4/9 + + if (winsOwner1) /* 6/9 */ !winsOwner2 || /* 4/9 */ winsType1 || /* 8/27 */ !winsType2 + else if (winsOwner2) /* 2/9 */ winsType1 && /* 2/27 */ !winsType2 + else /* 1/9 */ winsType1 || /* 2/27 */ !winsType2 + }} + + def narrowMostSpecific(alts: List[TermRef])(implicit ctx: Context): List[TermRef] = track("narrowMostSpecific") { + alts match { + case Nil => alts + case _ :: Nil => alts + case alt :: alts1 => + def winner(bestSoFar: TermRef, alts: List[TermRef]): TermRef = alts match { + case alt :: alts1 => + winner(if (isAsGood(alt, bestSoFar)) alt else bestSoFar, alts1) + case nil => + bestSoFar + } + val best = winner(alt, alts1) + def asGood(alts: List[TermRef]): List[TermRef] = alts match { + case alt :: alts1 => + if ((alt eq best) || !isAsGood(alt, best)) asGood(alts1) + else alt :: asGood(alts1) + case nil => + Nil + } + best :: asGood(alts) + } + } + + /** Resolve overloaded alternative `alts`, given expected type `pt` and + * possibly also type argument `targs` that need to be applied to each alternative + * to form the method type. + * todo: use techniques like for implicits to pick candidates quickly? + */ + def resolveOverloaded(alts: List[TermRef], pt: Type)(implicit ctx: Context): List[TermRef] = track("resolveOverloaded") { + + /** Is `alt` a method or polytype whose result type after the first value parameter + * section conforms to the expected type `resultType`? If `resultType` + * is a `IgnoredProto`, pick the underlying type instead. + */ + def resultConforms(alt: Type, resultType: Type)(implicit ctx: Context): Boolean = resultType match { + case IgnoredProto(ignored) => resultConforms(alt, ignored) + case _: ValueType => + alt.widen match { + case tp: PolyType => resultConforms(constrained(tp).resultType, resultType) + case tp: MethodType => constrainResult(tp.resultType, resultType) + case _ => true + } + case _ => true + } + + /** If the `chosen` alternative has a result type incompatible with the expected result + * type `pt`, run overloading resolution again on all alternatives that do match `pt`. + * If the latter succeeds with a single alternative, return it, otherwise + * fallback to `chosen`. + * + * Note this order of events is done for speed. One might be tempted to + * preselect alternatives by result type. But is slower, because it discriminates + * less. The idea is when searching for a best solution, as is the case in overloading + * resolution, we should first try criteria which are cheap and which have a high + * probability of pruning the search. result type comparisons are neither cheap nor + * do they prune much, on average. + */ + def adaptByResult(chosen: TermRef) = { + def nestedCtx = ctx.fresh.setExploreTyperState + pt match { + case pt: FunProto if !resultConforms(chosen, pt.resultType)(nestedCtx) => + alts.filter(alt => + (alt ne chosen) && resultConforms(alt, pt.resultType)(nestedCtx)) match { + case Nil => chosen + case alt2 :: Nil => alt2 + case alts2 => + resolveOverloaded(alts2, pt) match { + case alt2 :: Nil => alt2 + case _ => chosen + } + } + case _ => chosen + } + } + + var found = resolveOverloaded(alts, pt, Nil)(ctx.retractMode(Mode.ImplicitsEnabled)) + if (found.isEmpty && ctx.mode.is(Mode.ImplicitsEnabled)) + found = resolveOverloaded(alts, pt, Nil) + found match { + case alt :: Nil => adaptByResult(alt) :: Nil + case _ => found + } + } + + /** This private version of `resolveOverloaded` does the bulk of the work of + * overloading resolution, but does not do result adaptation. It might be + * called twice from the public `resolveOverloaded` method, once with + * implicits enabled, and once without. + */ + private def resolveOverloaded(alts: List[TermRef], pt: Type, targs: List[Type])(implicit ctx: Context): List[TermRef] = track("resolveOverloaded") { + + def isDetermined(alts: List[TermRef]) = alts.isEmpty || alts.tail.isEmpty + + /** The shape of given tree as a type; cannot handle named arguments. */ + def typeShape(tree: untpd.Tree): Type = tree match { + case untpd.Function(args, body) => + defn.FunctionOf(args map Function.const(defn.AnyType), typeShape(body)) + case _ => + defn.NothingType + } + + /** The shape of given tree as a type; is more expensive than + * typeShape but can can handle named arguments. + */ + def treeShape(tree: untpd.Tree): Tree = tree match { + case NamedArg(name, arg) => + val argShape = treeShape(arg) + cpy.NamedArg(tree)(name, argShape).withType(argShape.tpe) + case _ => + dummyTreeOfType(typeShape(tree)) + } + + def narrowByTypes(alts: List[TermRef], argTypes: List[Type], resultType: Type): List[TermRef] = + alts filter (isApplicable(_, argTypes, resultType)) + + val candidates = pt match { + case pt @ FunProto(args, resultType, _) => + val numArgs = args.length + val normArgs = args.mapConserve { + case Block(Nil, expr) => expr + case x => x + } + + def sizeFits(alt: TermRef, tp: Type): Boolean = tp match { + case tp: PolyType => sizeFits(alt, tp.resultType) + case MethodType(_, ptypes) => + val numParams = ptypes.length + def isVarArgs = ptypes.nonEmpty && ptypes.last.isRepeatedParam + def hasDefault = alt.symbol.hasDefaultParams + if (numParams == numArgs) true + else if (numParams < numArgs) isVarArgs + else if (numParams > numArgs + 1) hasDefault + else isVarArgs || hasDefault + case _ => + numArgs == 0 + } + + def narrowBySize(alts: List[TermRef]): List[TermRef] = + alts filter (alt => sizeFits(alt, alt.widen)) + + def narrowByShapes(alts: List[TermRef]): List[TermRef] = { + if (normArgs exists (_.isInstanceOf[untpd.Function])) + if (hasNamedArg(args)) narrowByTrees(alts, args map treeShape, resultType) + else narrowByTypes(alts, normArgs map typeShape, resultType) + else + alts + } + + def narrowByTrees(alts: List[TermRef], args: List[Tree], resultType: Type): List[TermRef] = { + val alts2 = alts.filter(alt => + isDirectlyApplicable(alt, targs, args, resultType) + ) + if (alts2.isEmpty && !ctx.isAfterTyper) + alts.filter(alt => + isApplicable(alt, targs, args, resultType) + ) + else + alts2 + } + + val alts1 = narrowBySize(alts) + //ctx.log(i"narrowed by size: ${alts1.map(_.symbol.showDcl)}%, %") + if (isDetermined(alts1)) alts1 + else { + val alts2 = narrowByShapes(alts1) + //ctx.log(i"narrowed by shape: ${alts1.map(_.symbol.showDcl)}%, %") + if (isDetermined(alts2)) alts2 + else { + pretypeArgs(alts2, pt) + narrowByTrees(alts2, pt.typedArgs, resultType) + } + } + + case pt @ PolyProto(targs1, pt1) => + assert(targs.isEmpty) + val alts1 = alts filter pt.isMatchedBy + resolveOverloaded(alts1, pt1, targs1) + + case defn.FunctionOf(args, resultType) => + narrowByTypes(alts, args, resultType) + + case pt => + alts filter (normalizedCompatible(_, pt)) + } + val found = narrowMostSpecific(candidates) + if (found.length <= 1) found + else { + val noDefaults = alts.filter(!_.symbol.hasDefaultParams) + if (noDefaults.length == 1) noDefaults // return unique alternative without default parameters if it exists + else { + val deepPt = pt.deepenProto + if (deepPt ne pt) resolveOverloaded(alts, deepPt, targs) + else alts + } + } + } + + /** 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 => + val sym = tpe(t).widen.classSymbol + if (sym.isNumericValueClass) numericClasses(ts1, acc + sym) + else Set() + case Nil => + acc + } + val clss = numericClasses(ts, Set()) + if (clss.size > 1) { + val lub = defn.ScalaNumericValueTypeList.find(lubTpe => + clss.forall(cls => defn.isValueSubType(cls.typeRef, lubTpe))).get + ts.mapConserve(adapt(_, lub)) + } + else ts + } + + /** If `trees` all have numeric value types, and they do not have all the same type, + * pick a common numeric supertype and convert all trees to this type. + */ + def harmonize(trees: List[Tree])(implicit ctx: Context): List[Tree] = { + def adapt(tree: Tree, pt: Type): Tree = tree match { + case cdef: CaseDef => tpd.cpy.CaseDef(cdef)(body = adapt(cdef.body, pt)) + case _ => adaptInterpolated(tree, pt, tree) + } + if (ctx.isAfterTyper) trees else harmonizeWith(trees)(_.tpe, adapt) + } + + /** If all `types` are numeric value types, and they are not all the same type, + * pick a common numeric supertype and return it instead of every original type. + */ + def harmonizeTypes(tpes: List[Type])(implicit ctx: Context): List[Type] = + harmonizeWith(tpes)(identity, (tp, pt) => pt) +} + diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala new file mode 100644 index 000000000..dbfc89f6c --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -0,0 +1,557 @@ +package dotty.tools +package dotc +package typer + +import core._ +import ast._ +import Contexts._ +import Types._ +import Flags._ +import Denotations._ +import Names._ +import StdNames._ +import NameOps._ +import Symbols._ +import Trees._ +import ProtoTypes._ +import Constants._ +import Scopes._ +import CheckRealizable._ +import ErrorReporting.errorTree +import annotation.unchecked +import util.Positions._ +import util.{Stats, SimpleMap} +import util.common._ +import transform.SymUtils._ +import Decorators._ +import Uniques._ +import ErrorReporting.{err, errorType} +import config.Printers.typr +import collection.mutable +import SymDenotations.NoCompleter + +object Checking { + import tpd._ + + /** A general checkBounds method that can be used for TypeApply nodes as + * well as for AppliedTypeTree nodes. Also checks that type arguments to + * *-type parameters are fully applied. + */ + def checkBounds(args: List[tpd.Tree], boundss: List[TypeBounds], instantiate: (Type, List[Type]) => Type)(implicit ctx: Context): Unit = { + (args, boundss).zipped.foreach { (arg, bound) => + if (!bound.isHK && arg.tpe.isHK) + ctx.error(ex"missing type parameter(s) for $arg", arg.pos) + } + for ((arg, which, bound) <- ctx.boundsViolations(args, boundss, instantiate)) + ctx.error( + ex"Type argument ${arg.tpe} does not conform to $which bound $bound ${err.whyNoMatchStr(arg.tpe, bound)}", + arg.pos.focus) + } + + /** Check that type arguments `args` conform to corresponding bounds in `poly` + * Note: This does not check the bounds of AppliedTypeTrees. These + * are handled by method checkBounds in FirstTransform + */ + def checkBounds(args: List[tpd.Tree], poly: PolyType)(implicit ctx: Context): Unit = + checkBounds(args, poly.paramBounds, _.substParams(poly, _)) + + /** Check applied type trees for well-formedness. This means + * - all arguments are within their corresponding bounds + * - if type is a higher-kinded application with wildcard arguments, + * check that it or one of its supertypes can be reduced to a normal application. + * Unreducible applications correspond to general existentials, and we + * cannot handle those. + */ + def checkAppliedType(tree: AppliedTypeTree)(implicit ctx: Context) = { + val AppliedTypeTree(tycon, args) = tree + // If `args` is a list of named arguments, return corresponding type parameters, + // otherwise return type parameters unchanged + val tparams = tycon.tpe.typeParams + def argNamed(tparam: TypeParamInfo) = args.find { + case NamedArg(name, _) => name == tparam.paramName + case _ => false + }.getOrElse(TypeTree(tparam.paramRef)) + val orderedArgs = if (hasNamedArg(args)) tparams.map(argNamed) else args + val bounds = tparams.map(_.paramBoundsAsSeenFrom(tycon.tpe)) + def instantiate(bound: Type, args: List[Type]) = + bound.LambdaAbstract(tparams).appliedTo(args) + checkBounds(orderedArgs, bounds, instantiate) + + def checkWildcardHKApply(tp: Type, pos: Position): Unit = tp match { + case tp @ HKApply(tycon, args) if args.exists(_.isInstanceOf[TypeBounds]) => + tycon match { + case tycon: PolyType => + ctx.errorOrMigrationWarning( + ex"unreducible application of higher-kinded type $tycon to wildcard arguments", + pos) + case _ => + checkWildcardHKApply(tp.superType, pos) + } + case _ => + } + def checkValidIfHKApply(implicit ctx: Context): Unit = + checkWildcardHKApply(tycon.tpe.appliedTo(args.map(_.tpe)), tree.pos) + checkValidIfHKApply(ctx.addMode(Mode.AllowLambdaWildcardApply)) + } + + /** Check that `tp` refers to a nonAbstract class + * and that the instance conforms to the self type of the created class. + */ + def checkInstantiable(tp: Type, pos: Position)(implicit ctx: Context): Unit = + tp.underlyingClassRef(refinementOK = false) match { + case tref: TypeRef => + val cls = tref.symbol + if (cls.is(AbstractOrTrait)) + ctx.error(em"$cls is abstract; cannot be instantiated", pos) + if (!cls.is(Module)) { + // Create a synthetic singleton type instance, and check whether + // it conforms to the self type of the class as seen from that instance. + val stp = SkolemType(tp) + val selfType = tref.givenSelfType.asSeenFrom(stp, cls) + if (selfType.exists && !(stp <:< selfType)) + ctx.error(ex"$tp does not conform to its self type $selfType; cannot be instantiated") + } + case _ => + } + + /** Check that type `tp` is realizable. */ + def checkRealizable(tp: Type, pos: Position)(implicit ctx: Context): Unit = { + val rstatus = realizability(tp) + if (rstatus ne Realizable) { + def msg = em"$tp is not a legal path\n since it${rstatus.msg}" + if (ctx.scala2Mode) ctx.migrationWarning(msg, pos) else ctx.error(msg, pos) + } + } + + /** A type map which checks that the only cycles in a type are F-bounds + * and that protects all F-bounded references by LazyRefs. + */ + class CheckNonCyclicMap(sym: Symbol, reportErrors: Boolean)(implicit ctx: Context) extends TypeMap { + + /** Are cycles allowed within nested refinedInfos of currently checked type? */ + private var nestedCycleOK = false + + /** Are cycles allowed within currently checked type? */ + private var cycleOK = false + + /** A diagnostic output string that indicates the position of the last + * part of a type bounds checked by checkInfo. Possible choices: + * alias, lower bound, upper bound. + */ + var where: String = "" + + /** The last type top-level type checked when a CyclicReference occurs. */ + var lastChecked: Type = NoType + + /** Check info `tp` for cycles. Throw CyclicReference for illegal cycles, + * break direct cycle with a LazyRef for legal, F-bounded cycles. + */ + def checkInfo(tp: Type): Type = tp match { + case tp @ TypeAlias(alias) => + try tp.derivedTypeAlias(apply(alias)) + finally { + where = "alias" + lastChecked = alias + } + case tp @ TypeBounds(lo, hi) => + val lo1 = try apply(lo) finally { + where = "lower bound" + lastChecked = lo + } + val saved = nestedCycleOK + nestedCycleOK = true + try tp.derivedTypeBounds(lo1, apply(hi)) + finally { + nestedCycleOK = saved + where = "upper bound" + lastChecked = hi + } + case _ => + tp + } + + private def apply(tp: Type, cycleOK: Boolean, nestedCycleOK: Boolean): Type = { + val savedCycleOK = this.cycleOK + val savedNestedCycleOK = this.nestedCycleOK + this.cycleOK = cycleOK + this.nestedCycleOK = nestedCycleOK + try apply(tp) + finally { + this.cycleOK = savedCycleOK + this.nestedCycleOK = savedNestedCycleOK + } + } + + def apply(tp: Type): Type = tp match { + case tp: TermRef => + this(tp.info) + mapOver(tp) + case tp @ RefinedType(parent, name, rinfo) => + tp.derivedRefinedType(this(parent), name, this(rinfo, nestedCycleOK, nestedCycleOK)) + case tp: RecType => + tp.rebind(this(tp.parent)) + case tp @ HKApply(tycon, args) => + tp.derivedAppliedType(this(tycon), args.map(this(_, nestedCycleOK, nestedCycleOK))) + case tp @ TypeRef(pre, name) => + try { + // A prefix is interesting if it might contain (transitively) a reference + // to symbol `sym` itself. We only check references with interesting + // prefixes for cycles. This pruning is done in order not to force + // global symbols when doing the cyclicity check. + def isInteresting(prefix: Type): Boolean = prefix.stripTypeVar match { + case NoPrefix => true + case prefix: ThisType => sym.owner.isClass && prefix.cls.isContainedIn(sym.owner) + case prefix: NamedType => !prefix.symbol.isStaticOwner && isInteresting(prefix.prefix) + case SuperType(thistp, _) => isInteresting(thistp) + case AndType(tp1, tp2) => isInteresting(tp1) || isInteresting(tp2) + case OrType(tp1, tp2) => isInteresting(tp1) && isInteresting(tp2) + case _: RefinedOrRecType | _: HKApply => true + case _ => false + } + if (isInteresting(pre)) { + val pre1 = this(pre, false, false) + checkInfo(tp.info) + if (pre1 eq pre) tp else tp.newLikeThis(pre1) + } + else tp + } catch { + case ex: CyclicReference => + ctx.debuglog(i"cycle detected for $tp, $nestedCycleOK, $cycleOK") + if (cycleOK) LazyRef(() => tp) + else if (reportErrors) throw ex + else tp + } + case _ => mapOver(tp) + } + } + + /** Check that `info` of symbol `sym` is not cyclic. + * @pre sym is not yet initialized (i.e. its type is a Completer). + * @return `info` where every legal F-bounded reference is proctected + * by a `LazyRef`, or `ErrorType` if a cycle was detected and reported. + */ + def checkNonCyclic(sym: Symbol, info: Type, reportErrors: Boolean)(implicit ctx: Context): Type = { + val checker = new CheckNonCyclicMap(sym, reportErrors)(ctx.addMode(Mode.CheckCyclic)) + try checker.checkInfo(info) + catch { + case ex: CyclicReference => + if (reportErrors) { + ctx.error(i"illegal cyclic reference: ${checker.where} ${checker.lastChecked} of $sym refers back to the type itself", sym.pos) + ErrorType + } + else info + } + } + + /** Check that refinement satisfies the following two conditions + * 1. No part of it refers to a symbol that's defined in the same refinement + * at a textually later point. + * 2. All references to the refinement itself via `this` are followed by + * selections. + * Note: It's not yet clear what exactly we want to allow and what we want to rule out. + * This depends also on firming up the DOT calculus. For the moment we only issue + * deprecated warnings, not errors. + */ + def checkRefinementNonCyclic(refinement: Tree, refineCls: ClassSymbol, seen: mutable.Set[Symbol]) + (implicit ctx: Context): Unit = { + def flag(what: String, tree: Tree) = + ctx.deprecationWarning(i"$what reference in refinement is deprecated", tree.pos) + def forwardRef(tree: Tree) = flag("forward", tree) + def selfRef(tree: Tree) = flag("self", tree) + val checkTree = new TreeAccumulator[Unit] { + def checkRef(tree: Tree, sym: Symbol) = + if (sym.maybeOwner == refineCls && !seen(sym)) forwardRef(tree) + def apply(x: Unit, tree: Tree)(implicit ctx: Context) = tree match { + case tree: MemberDef => + foldOver(x, tree) + seen += tree.symbol + case tree @ Select(This(_), _) => + checkRef(tree, tree.symbol) + case tree: RefTree => + checkRef(tree, tree.symbol) + foldOver(x, tree) + case tree: This => + selfRef(tree) + case tree: TypeTree => + val checkType = new TypeAccumulator[Unit] { + def apply(x: Unit, tp: Type): Unit = tp match { + case tp: NamedType => + checkRef(tree, tp.symbol) + tp.prefix match { + case pre: ThisType => + case pre => foldOver(x, pre) + } + case tp: ThisType if tp.cls == refineCls => + selfRef(tree) + case _ => + foldOver(x, tp) + } + } + checkType((), tree.tpe) + case _ => + foldOver(x, tree) + } + } + checkTree((), refinement) + } + + /** Check that symbol's definition is well-formed. */ + def checkWellFormed(sym: Symbol)(implicit ctx: Context): Unit = { + //println(i"check wf $sym with flags ${sym.flags}") + def fail(msg: String) = ctx.error(msg, sym.pos) + def varNote = + if (sym.is(Mutable)) "\n(Note that variables need to be initialized to be defined)" + else "" + + def checkWithDeferred(flag: FlagSet) = + if (sym.is(flag)) + fail(i"abstract member may not have `$flag' modifier") + def checkNoConflict(flag1: FlagSet, flag2: FlagSet) = + if (sym.is(allOf(flag1, flag2))) + fail(i"illegal combination of modifiers: $flag1 and $flag2 for: $sym") + + if (sym.is(ImplicitCommon)) { + if (sym.owner.is(Package)) + fail(i"`implicit' modifier cannot be used for top-level definitions") + if (sym.isType) + fail(i"`implicit' modifier cannot be used for types or traits") + } + if (!sym.isClass && sym.is(Abstract)) + fail(i"`abstract' modifier can be used only for classes; it should be omitted for abstract members") + if (sym.is(AbsOverride) && !sym.owner.is(Trait)) + fail(i"`abstract override' modifier only allowed for members of traits") + if (sym.is(Trait) && sym.is(Final)) + fail(i"$sym may not be `final'") + if (sym.hasAnnotation(defn.NativeAnnot)) { + if (!sym.is(Deferred)) + fail(i"`@native' members may not have implementation") + } + else if (sym.is(Deferred, butNot = Param) && !sym.isSelfSym) { + if (!sym.owner.isClass || sym.owner.is(Module) || sym.owner.isAnonymousClass) + fail(i"only classes can have declared but undefined members$varNote") + checkWithDeferred(Private) + checkWithDeferred(Final) + checkWithDeferred(Inline) + } + if (sym.isValueClass && sym.is(Trait) && !sym.isRefinementClass) + fail(i"$sym cannot extend AnyVal") + checkNoConflict(Final, Sealed) + checkNoConflict(Private, Protected) + checkNoConflict(Abstract, Override) + } + + /** Check the type signature of the symbol `M` defined by `tree` does not refer + * to a private type or value which is invisible at a point where `M` is still + * visible. As an exception, we allow references to type aliases if the underlying + * type of the alias is not a leak. So type aliases are transparent as far as + * leak testing is concerned. + * @return The `info` of `sym`, with problematic aliases expanded away. + * See i997.scala for tests, i1130.scala for a case where it matters that we + * transform leaky aliases away. + */ + def checkNoPrivateLeaks(sym: Symbol, pos: Position)(implicit ctx: Context): Type = { + class NotPrivate extends TypeMap { + type Errors = List[(String, Position)] + var errors: Errors = Nil + def accessBoundary(sym: Symbol): Symbol = + if (sym.is(Private)) sym.owner + else if (sym.privateWithin.exists) sym.privateWithin + else if (sym.is(Package)) sym + else accessBoundary(sym.owner) + def apply(tp: Type): Type = tp match { + case tp: NamedType => + val prevErrors = errors + var tp1 = + if (tp.symbol.is(Private) && + !accessBoundary(sym).isContainedIn(tp.symbol.owner)) { + errors = (em"non-private $sym refers to private ${tp.symbol}\n in its type signature ${sym.info}", + sym.pos) :: errors + tp + } + else mapOver(tp) + if ((errors ne prevErrors) && tp.info.isAlias) { + // try to dealias to avoid a leak error + val savedErrors = errors + errors = prevErrors + val tp2 = apply(tp.superType) + if (errors eq prevErrors) tp1 = tp2 + else errors = savedErrors + } + tp1 + case tp: ClassInfo => + tp.derivedClassInfo( + prefix = apply(tp.prefix), + classParents = tp.parentsWithArgs.map(p => + apply(p).underlyingClassRef(refinementOK = false).asInstanceOf[TypeRef])) + case _ => + mapOver(tp) + } + } + val notPrivate = new NotPrivate + val info = notPrivate(sym.info) + notPrivate.errors.foreach { case (msg, pos) => ctx.errorOrMigrationWarning(msg, pos) } + info + } +} + +trait Checking { + + import tpd._ + + def checkNonCyclic(sym: Symbol, info: TypeBounds, reportErrors: Boolean)(implicit ctx: Context): Type = + Checking.checkNonCyclic(sym, info, reportErrors) + + /** Check that Java statics and packages can only be used in selections. + */ + def checkValue(tree: Tree, proto: Type)(implicit ctx: Context): tree.type = { + if (!proto.isInstanceOf[SelectionProto]) { + val sym = tree.tpe.termSymbol + // The check is avoided inside Java compilation units because it always fails + // on the singleton type Module.type. + if ((sym is Package) || ((sym is JavaModule) && !ctx.compilationUnit.isJava)) ctx.error(em"$sym is not a value", tree.pos) + } + tree + } + + /** Check that type `tp` is stable. */ + def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit = + if (!tp.isStable) ctx.error(ex"$tp is not stable", pos) + + /** Check that all type members of `tp` have realizable bounds */ + def checkRealizableBounds(tp: Type, pos: Position)(implicit ctx: Context): Unit = { + val rstatus = boundsRealizability(tp) + if (rstatus ne Realizable) + ctx.error(ex"$tp cannot be instantiated since it${rstatus.msg}", pos) + } + + /** Check that `tp` is a class type. + * Also, if `traitReq` is true, check that `tp` is a trait. + * Also, if `stablePrefixReq` is true and phase is not after RefChecks, + * check that class prefix is stable. + * @return `tp` itself if it is a class or trait ref, ObjectType if not. + */ + def checkClassType(tp: Type, pos: Position, traitReq: Boolean, stablePrefixReq: Boolean)(implicit ctx: Context): Type = + tp.underlyingClassRef(refinementOK = false) match { + case tref: TypeRef => + if (traitReq && !(tref.symbol is Trait)) ctx.error(ex"$tref is not a trait", pos) + if (stablePrefixReq && ctx.phase <= ctx.refchecksPhase) checkStable(tref.prefix, pos) + tp + case _ => + ctx.error(ex"$tp is not a class type", pos) + defn.ObjectType + } + + /** Check that a non-implicit parameter making up the first parameter section of an + * implicit conversion is not a singleton type. + */ + def checkImplicitParamsNotSingletons(vparamss: List[List[ValDef]])(implicit ctx: Context): Unit = vparamss match { + case (vparam :: Nil) :: _ if !(vparam.symbol is Implicit) => + if (vparam.tpt.tpe.isInstanceOf[SingletonType]) + ctx.error(s"implicit conversion may not have a parameter of singleton type", vparam.tpt.pos) + case _ => + } + + /** Check that any top-level type arguments in this type are feasible, i.e. that + * their lower bound conforms to their upper bound. If a type argument is + * infeasible, issue and error and continue with upper bound. + */ + def checkFeasible(tp: Type, pos: Position, where: => String = "")(implicit ctx: Context): Type = tp match { + case tp: RefinedType => + tp.derivedRefinedType(tp.parent, tp.refinedName, checkFeasible(tp.refinedInfo, pos, where)) + case tp: RecType => + tp.rebind(tp.parent) + case tp @ TypeBounds(lo, hi) if !(lo <:< hi) => + ctx.error(ex"no type exists between low bound $lo and high bound $hi$where", pos) + TypeAlias(hi) + case _ => + tp + } + + /** Check that `tree` is a pure expression of constant type */ + def checkInlineConformant(tree: Tree, what: => String)(implicit ctx: Context): Unit = + tree.tpe match { + case tp: TermRef if tp.symbol.is(InlineParam) => // ok + case tp => tp.widenTermRefExpr match { + case tp: ConstantType if isPureExpr(tree) => // ok + case tp if defn.isFunctionType(tp) && isPureExpr(tree) => // ok + case _ => ctx.error(em"$what must be a constant expression or a function", tree.pos) + } + } + + /** Check that class does not define same symbol twice */ + def checkNoDoubleDefs(cls: Symbol)(implicit ctx: Context): Unit = { + val seen = new mutable.HashMap[Name, List[Symbol]] { + override def default(key: Name) = Nil + } + typr.println(i"check no double defs $cls") + + def checkDecl(decl: Symbol): Unit = { + for (other <- seen(decl.name)) { + typr.println(i"conflict? $decl $other") + if (decl.matches(other)) { + def doubleDefError(decl: Symbol, other: Symbol): Unit = { + def ofType = if (decl.isType) "" else em": ${other.info}" + def explanation = + if (!decl.isRealMethod) "" + else "\n (the definitions have matching type signatures)" + ctx.error(em"$decl is already defined as $other$ofType$explanation", decl.pos) + } + if (decl is Synthetic) doubleDefError(other, decl) + else doubleDefError(decl, other) + } + if ((decl is HasDefaultParams) && (other is HasDefaultParams)) { + ctx.error(em"two or more overloaded variants of $decl have default arguments") + decl resetFlag HasDefaultParams + } + } + seen(decl.name) = decl :: seen(decl.name) + } + + cls.info.decls.foreach(checkDecl) + cls.info match { + case ClassInfo(_, _, _, _, selfSym: Symbol) => checkDecl(selfSym) + case _ => + } + } + + def checkParentCall(call: Tree, caller: ClassSymbol)(implicit ctx: Context) = + if (!ctx.isAfterTyper) { + val called = call.tpe.classSymbol + if (caller is Trait) + ctx.error(i"$caller may not call constructor of $called", call.pos) + else if (called.is(Trait) && !caller.mixins.contains(called)) + ctx.error(i"""$called is already implemented by super${caller.superClass}, + |its constructor cannot be called again""", call.pos) + } + + /** Check that `tpt` does not define a higher-kinded type */ + def checkSimpleKinded(tpt: Tree)(implicit ctx: Context): Tree = + if (tpt.tpe.isHK && !ctx.compilationUnit.isJava) { + // be more lenient with missing type params in Java, + // needed to make pos/java-interop/t1196 work. + errorTree(tpt, ex"missing type parameter for ${tpt.tpe}") + } + else tpt + + /** Check that `tpt` does not refer to a singleton type */ + def checkNotSingleton(tpt: Tree, where: String)(implicit ctx: Context): Tree = + if (tpt.tpe.isInstanceOf[SingletonType]) { + errorTree(tpt, ex"Singleton type ${tpt.tpe} is not allowed $where") + } + else tpt +} + +trait NoChecking extends Checking { + import tpd._ + override def checkNonCyclic(sym: Symbol, info: TypeBounds, reportErrors: Boolean)(implicit ctx: Context): Type = info + override def checkValue(tree: Tree, proto: Type)(implicit ctx: Context): tree.type = tree + override def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit = () + override def checkClassType(tp: Type, pos: Position, traitReq: Boolean, stablePrefixReq: Boolean)(implicit ctx: Context): Type = tp + override def checkImplicitParamsNotSingletons(vparamss: List[List[ValDef]])(implicit ctx: Context): Unit = () + override def checkFeasible(tp: Type, pos: Position, where: => String = "")(implicit ctx: Context): Type = tp + override def checkInlineConformant(tree: Tree, what: => String)(implicit ctx: Context) = () + override def checkNoDoubleDefs(cls: Symbol)(implicit ctx: Context): Unit = () + override def checkParentCall(call: Tree, caller: ClassSymbol)(implicit ctx: Context) = () + override def checkSimpleKinded(tpt: Tree)(implicit ctx: Context): Tree = tpt + override def checkNotSingleton(tpt: Tree, where: String)(implicit ctx: Context): Tree = tpt +} diff --git a/compiler/src/dotty/tools/dotc/typer/ConstFold.scala b/compiler/src/dotty/tools/dotc/typer/ConstFold.scala new file mode 100644 index 000000000..68a5d05f5 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/ConstFold.scala @@ -0,0 +1,182 @@ +package dotty.tools.dotc +package typer + +import java.lang.ArithmeticException + +import ast._ +import Trees._ +import core._ +import Types._ +import Constants._ +import Names._ +import StdNames._ +import Contexts._ + +object ConstFold { + + import tpd._ + + /** If tree is a constant operation, replace with result. */ + def apply(tree: Tree)(implicit ctx: Context): Tree = finish(tree) { + tree match { + case Apply(Select(xt, op), yt :: Nil) => + xt.tpe.widenTermRefExpr match { + case ConstantType(x) => + yt.tpe.widenTermRefExpr match { + case ConstantType(y) => foldBinop(op, x, y) + case _ => null + } + case _ => null + } + case Select(xt, op) => + xt.tpe.widenTermRefExpr match { + case ConstantType(x) => foldUnop(op, x) + case _ => null + } + case _ => null + } + } + + /** If tree is a constant value that can be converted to type `pt`, perform + * the conversion. + */ + def apply(tree: Tree, pt: Type)(implicit ctx: Context): Tree = + finish(apply(tree)) { + tree.tpe.widenTermRefExpr match { + case ConstantType(x) => x convertTo pt + case _ => null + } + } + + private def finish(tree: Tree)(compX: => Constant)(implicit ctx: Context): Tree = + try { + val x = compX + if (x ne null) tree withType ConstantType(x) + else tree + } catch { + case _: ArithmeticException => tree // the code will crash at runtime, + // but that is better than the + // compiler itself crashing + } + + private def foldUnop(op: Name, x: Constant): Constant = (op, x.tag) match { + case (nme.UNARY_!, BooleanTag) => Constant(!x.booleanValue) + + case (nme.UNARY_~ , IntTag ) => Constant(~x.intValue) + case (nme.UNARY_~ , LongTag ) => Constant(~x.longValue) + + case (nme.UNARY_+ , IntTag ) => Constant(x.intValue) + case (nme.UNARY_+ , LongTag ) => Constant(x.longValue) + case (nme.UNARY_+ , FloatTag ) => Constant(x.floatValue) + case (nme.UNARY_+ , DoubleTag ) => Constant(x.doubleValue) + + case (nme.UNARY_- , IntTag ) => Constant(-x.intValue) + case (nme.UNARY_- , LongTag ) => Constant(-x.longValue) + case (nme.UNARY_- , FloatTag ) => Constant(-x.floatValue) + case (nme.UNARY_- , DoubleTag ) => Constant(-x.doubleValue) + + case _ => null + } + + /** These are local helpers to keep foldBinop from overly taxing the + * optimizer. + */ + private def foldBooleanOp(op: Name, x: Constant, y: Constant): Constant = op match { + case nme.ZOR => Constant(x.booleanValue | y.booleanValue) + case nme.OR => Constant(x.booleanValue | y.booleanValue) + case nme.XOR => Constant(x.booleanValue ^ y.booleanValue) + case nme.ZAND => Constant(x.booleanValue & y.booleanValue) + case nme.AND => Constant(x.booleanValue & y.booleanValue) + case nme.EQ => Constant(x.booleanValue == y.booleanValue) + case nme.NE => Constant(x.booleanValue != y.booleanValue) + case _ => null + } + private def foldSubrangeOp(op: Name, x: Constant, y: Constant): Constant = op match { + case nme.OR => Constant(x.intValue | y.intValue) + case nme.XOR => Constant(x.intValue ^ y.intValue) + case nme.AND => Constant(x.intValue & y.intValue) + case nme.LSL => Constant(x.intValue << y.intValue) + case nme.LSR => Constant(x.intValue >>> y.intValue) + case nme.ASR => Constant(x.intValue >> y.intValue) + case nme.EQ => Constant(x.intValue == y.intValue) + case nme.NE => Constant(x.intValue != y.intValue) + case nme.LT => Constant(x.intValue < y.intValue) + case nme.GT => Constant(x.intValue > y.intValue) + case nme.LE => Constant(x.intValue <= y.intValue) + case nme.GE => Constant(x.intValue >= y.intValue) + case nme.ADD => Constant(x.intValue + y.intValue) + case nme.SUB => Constant(x.intValue - y.intValue) + case nme.MUL => Constant(x.intValue * y.intValue) + case nme.DIV => Constant(x.intValue / y.intValue) + case nme.MOD => Constant(x.intValue % y.intValue) + case _ => null + } + private def foldLongOp(op: Name, x: Constant, y: Constant): Constant = op match { + case nme.OR => Constant(x.longValue | y.longValue) + case nme.XOR => Constant(x.longValue ^ y.longValue) + case nme.AND => Constant(x.longValue & y.longValue) + case nme.LSL => Constant(x.longValue << y.longValue) + case nme.LSR => Constant(x.longValue >>> y.longValue) + case nme.ASR => Constant(x.longValue >> y.longValue) + case nme.EQ => Constant(x.longValue == y.longValue) + case nme.NE => Constant(x.longValue != y.longValue) + case nme.LT => Constant(x.longValue < y.longValue) + case nme.GT => Constant(x.longValue > y.longValue) + case nme.LE => Constant(x.longValue <= y.longValue) + case nme.GE => Constant(x.longValue >= y.longValue) + case nme.ADD => Constant(x.longValue + y.longValue) + case nme.SUB => Constant(x.longValue - y.longValue) + case nme.MUL => Constant(x.longValue * y.longValue) + case nme.DIV => Constant(x.longValue / y.longValue) + case nme.MOD => Constant(x.longValue % y.longValue) + case _ => null + } + private def foldFloatOp(op: Name, x: Constant, y: Constant): Constant = op match { + case nme.EQ => Constant(x.floatValue == y.floatValue) + case nme.NE => Constant(x.floatValue != y.floatValue) + case nme.LT => Constant(x.floatValue < y.floatValue) + case nme.GT => Constant(x.floatValue > y.floatValue) + case nme.LE => Constant(x.floatValue <= y.floatValue) + case nme.GE => Constant(x.floatValue >= y.floatValue) + case nme.ADD => Constant(x.floatValue + y.floatValue) + case nme.SUB => Constant(x.floatValue - y.floatValue) + case nme.MUL => Constant(x.floatValue * y.floatValue) + case nme.DIV => Constant(x.floatValue / y.floatValue) + case nme.MOD => Constant(x.floatValue % y.floatValue) + case _ => null + } + private def foldDoubleOp(op: Name, x: Constant, y: Constant): Constant = op match { + case nme.EQ => Constant(x.doubleValue == y.doubleValue) + case nme.NE => Constant(x.doubleValue != y.doubleValue) + case nme.LT => Constant(x.doubleValue < y.doubleValue) + case nme.GT => Constant(x.doubleValue > y.doubleValue) + case nme.LE => Constant(x.doubleValue <= y.doubleValue) + case nme.GE => Constant(x.doubleValue >= y.doubleValue) + case nme.ADD => Constant(x.doubleValue + y.doubleValue) + case nme.SUB => Constant(x.doubleValue - y.doubleValue) + case nme.MUL => Constant(x.doubleValue * y.doubleValue) + case nme.DIV => Constant(x.doubleValue / y.doubleValue) + case nme.MOD => Constant(x.doubleValue % y.doubleValue) + case _ => null + } + + private def foldBinop(op: Name, x: Constant, y: Constant): Constant = { + val optag = + if (x.tag == y.tag) x.tag + else if (x.isNumeric && y.isNumeric) math.max(x.tag, y.tag) + else NoTag + + try optag match { + case BooleanTag => foldBooleanOp(op, x, y) + case ByteTag | ShortTag | CharTag | IntTag => foldSubrangeOp(op, x, y) + case LongTag => foldLongOp(op, x, y) + case FloatTag => foldFloatOp(op, x, y) + case DoubleTag => foldDoubleOp(op, x, y) + case StringTag if op == nme.ADD => Constant(x.stringValue + y.stringValue) + case _ => null + } + catch { + case ex: ArithmeticException => null + } + } +} diff --git a/compiler/src/dotty/tools/dotc/typer/Docstrings.scala b/compiler/src/dotty/tools/dotc/typer/Docstrings.scala new file mode 100644 index 000000000..370844e65 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/Docstrings.scala @@ -0,0 +1,56 @@ +package dotty.tools +package dotc +package typer + +import core._ +import Contexts._, Symbols._, Decorators._, Comments._ +import util.Positions._ +import ast.tpd + +trait Docstrings { self: Typer => + + /** The Docstrings typer will handle the expansion of `@define` and + * `@inheritdoc` if there is a `DocContext` present as a property in the + * supplied `ctx`. + * + * It will also type any `@usecase` available in function definitions. + */ + def cookComments(syms: List[Symbol], owner: Symbol)(implicit ctx: Context): Unit = + ctx.docCtx.foreach { docbase => + val relevantSyms = syms.filter(docbase.docstring(_).isDefined) + relevantSyms.foreach { sym => + expandParentDocs(sym) + val usecases = docbase.docstring(sym).map(_.usecases).getOrElse(Nil) + + usecases.foreach { usecase => + enterSymbol(createSymbol(usecase.untpdCode)) + + typedStats(usecase.untpdCode :: Nil, owner) match { + case List(df: tpd.DefDef) => usecase.tpdCode = df + case _ => ctx.error("`@usecase` was not a valid definition", usecase.codePos) + } + } + } + } + + private def expandParentDocs(sym: Symbol)(implicit ctx: Context): Unit = + ctx.docCtx.foreach { docCtx => + docCtx.docstring(sym).foreach { cmt => + def expandDoc(owner: Symbol): Unit = if (!cmt.isExpanded) { + val tplExp = docCtx.templateExpander + tplExp.defineVariables(sym) + + val newCmt = cmt + .expand(tplExp.expandedDocComment(sym, owner, _)) + .withUsecases + + docCtx.addDocstring(sym, Some(newCmt)) + } + + if (sym ne NoSymbol) { + expandParentDocs(sym.owner) + expandDoc(sym.owner) + } + } + } +} diff --git a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala new file mode 100644 index 000000000..b5ace87d3 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala @@ -0,0 +1,104 @@ +package dotty.tools +package dotc +package typer + +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.ast.untpd +import dotty.tools.dotc.core.Constants.Constant +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Names.Name +import dotty.tools.dotc.core.StdNames._ +import dotty.tools.dotc.core.Types._ +import dotty.tools.dotc.core.Decorators._ + +object Dynamic { + def isDynamicMethod(name: Name): Boolean = + name == nme.applyDynamic || name == nme.selectDynamic || name == nme.updateDynamic || name == nme.applyDynamicNamed +} + +/** Translates selection that does not typecheck according to the scala.Dynamic rules: + * foo.bar(baz) = quux ~~> foo.selectDynamic(bar).update(baz, quux) + * foo.bar = baz ~~> foo.updateDynamic("bar")(baz) + * foo.bar(x = bazX, y = bazY, baz, ...) ~~> foo.applyDynamicNamed("bar")(("x", bazX), ("y", bazY), ("", baz), ...) + * foo.bar(baz0, baz1, ...) ~~> foo.applyDynamic(bar)(baz0, baz1, ...) + * foo.bar ~~> foo.selectDynamic(bar) + * + * The first matching rule of is applied. + */ +trait Dynamic { self: Typer with Applications => + import Dynamic._ + import tpd._ + + /** Translate selection that does not typecheck according to the normal rules into a applyDynamic/applyDynamicNamed. + * foo.bar(baz0, baz1, ...) ~~> foo.applyDynamic(bar)(baz0, baz1, ...) + * foo.bar[T0, ...](baz0, baz1, ...) ~~> foo.applyDynamic[T0, ...](bar)(baz0, baz1, ...) + * foo.bar(x = bazX, y = bazY, baz, ...) ~~> foo.applyDynamicNamed("bar")(("x", bazX), ("y", bazY), ("", baz), ...) + * foo.bar[T0, ...](x = bazX, y = bazY, baz, ...) ~~> foo.applyDynamicNamed[T0, ...]("bar")(("x", bazX), ("y", bazY), ("", baz), ...) + */ + def typedDynamicApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = { + def typedDynamicApply(qual: untpd.Tree, name: Name, targs: List[untpd.Tree]): Tree = { + def isNamedArg(arg: untpd.Tree): Boolean = arg match { case NamedArg(_, _) => true; case _ => false } + val args = tree.args + val dynName = if (args.exists(isNamedArg)) nme.applyDynamicNamed else nme.applyDynamic + if (dynName == nme.applyDynamicNamed && untpd.isWildcardStarArgList(args)) { + ctx.error("applyDynamicNamed does not support passing a vararg parameter", tree.pos) + tree.withType(ErrorType) + } else { + def namedArgTuple(name: String, arg: untpd.Tree) = untpd.Tuple(List(Literal(Constant(name)), arg)) + def namedArgs = args.map { + case NamedArg(argName, arg) => namedArgTuple(argName.toString, arg) + case arg => namedArgTuple("", arg) + } + val args1 = if (dynName == nme.applyDynamic) args else namedArgs + typedApply(untpd.Apply(coreDynamic(qual, dynName, name, targs), args1), pt) + } + } + + tree.fun match { + case Select(qual, name) if !isDynamicMethod(name) => + typedDynamicApply(qual, name, Nil) + case TypeApply(Select(qual, name), targs) if !isDynamicMethod(name) => + typedDynamicApply(qual, name, targs) + case TypeApply(fun, targs) => + typedDynamicApply(fun, nme.apply, targs) + case fun => + typedDynamicApply(fun, nme.apply, Nil) + } + } + + /** Translate selection that does not typecheck according to the normal rules into a selectDynamic. + * foo.bar ~~> foo.selectDynamic(bar) + * foo.bar[T0, ...] ~~> foo.selectDynamic[T0, ...](bar) + * + * Note: inner part of translation foo.bar(baz) = quux ~~> foo.selectDynamic(bar).update(baz, quux) is achieved + * through an existing transformation of in typedAssign [foo.bar(baz) = quux ~~> foo.bar.update(baz, quux)]. + */ + def typedDynamicSelect(tree: untpd.Select, targs: List[Tree], pt: Type)(implicit ctx: Context): Tree = + typedApply(coreDynamic(tree.qualifier, nme.selectDynamic, tree.name, targs), pt) + + /** Translate selection that does not typecheck according to the normal rules into a updateDynamic. + * foo.bar = baz ~~> foo.updateDynamic(bar)(baz) + */ + def typedDynamicAssign(tree: untpd.Assign, pt: Type)(implicit ctx: Context): Tree = { + def typedDynamicAssign(qual: untpd.Tree, name: Name, targs: List[untpd.Tree]): Tree = + typedApply(untpd.Apply(coreDynamic(qual, nme.updateDynamic, name, targs), tree.rhs), pt) + tree.lhs match { + case Select(qual, name) if !isDynamicMethod(name) => + typedDynamicAssign(qual, name, Nil) + case TypeApply(Select(qual, name), targs) if !isDynamicMethod(name) => + typedDynamicAssign(qual, name, targs) + case _ => + ctx.error("reassignment to val", tree.pos) + tree.withType(ErrorType) + } + } + + private def coreDynamic(qual: untpd.Tree, dynName: Name, name: Name, targs: List[untpd.Tree])(implicit ctx: Context): untpd.Apply = { + val select = untpd.Select(qual, dynName) + val selectWithTypes = + if (targs.isEmpty) select + else untpd.TypeApply(select, targs) + untpd.Apply(selectWithTypes, Literal(Constant(name.toString))) + } +} diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala new file mode 100644 index 000000000..a18c83ff8 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -0,0 +1,153 @@ +package dotty.tools +package dotc +package typer + +import ast._ +import core._ +import Trees._ +import Types._, ProtoTypes._, Contexts._, Decorators._, Denotations._, Symbols._ +import Applications._, Implicits._, Flags._ +import util.Positions._ +import printing.{Showable, RefinedPrinter} +import scala.collection.mutable +import java.util.regex.Matcher.quoteReplacement +import reporting.diagnostic.Message +import reporting.diagnostic.messages._ + +object ErrorReporting { + + import tpd._ + + def errorTree(tree: untpd.Tree, msg: => Message)(implicit ctx: Context): tpd.Tree = + tree withType errorType(msg, tree.pos) + + def errorType(msg: => Message, pos: Position)(implicit ctx: Context): ErrorType = { + ctx.error(msg, pos) + ErrorType + } + + def cyclicErrorMsg(ex: CyclicReference)(implicit ctx: Context) = { + val cycleSym = ex.denot.symbol + def errorMsg(msg: String, cx: Context): String = + if (cx.mode is Mode.InferringReturnType) { + cx.tree match { + case tree: untpd.ValOrDefDef => + // Dotty deviation: Was Trees.ValOrDefDef[_], but this gives ValOrDefDef[Nothing] instead of + // ValOrDefDel[Null]. Scala handles it, but it looks accidental because bounds propagation + // fails if the parameter is invariant or cotravariant. + // See test pending/pos/boundspropagation.scala + val treeSym = ctx.symOfContextTree(tree) + if (treeSym.exists && treeSym.name == cycleSym.name && treeSym.owner == cycleSym.owner) { + val result = if (cycleSym is Method) " result" else "" + em"overloaded or recursive $cycleSym needs$result type" + } + else errorMsg(msg, cx.outer) + case _ => + errorMsg(msg, cx.outer) + } + } else msg + errorMsg(ex.show, ctx) + } + + def wrongNumberOfArgs(fntpe: Type, kind: String, expectedArgs: List[TypeParamInfo], actual: List[untpd.Tree], pos: Position)(implicit ctx: Context) = + errorType(WrongNumberOfArgs(fntpe, kind, expectedArgs, actual)(ctx), pos) + + class Errors(implicit ctx: Context) { + + /** An explanatory note to be added to error messages + * when there's a problem with abstract var defs */ + def abstractVarMessage(sym: Symbol): String = + if (sym.underlyingSymbol.is(Mutable)) + "\n(Note that variables need to be initialized to be defined)" + else "" + + def expectedTypeStr(tp: Type): String = tp match { + case tp: PolyProto => + em"type arguments [${tp.targs}%, %] and ${expectedTypeStr(tp.resultType)}" + case tp: FunProto => + val result = tp.resultType match { + case _: WildcardType | _: IgnoredProto => "" + case tp => em" and expected result type $tp" + } + em"arguments (${tp.typedArgs.tpes}%, %)$result" + case _ => + em"expected type $tp" + } + + def anonymousTypeMemberStr(tpe: Type) = { + val kind = tpe match { + case _: TypeBounds => "type with bounds" + case _: PolyType | _: MethodType => "method" + case _ => "value of type" + } + em"$kind $tpe" + } + + def overloadedAltsStr(alts: List[SingleDenotation]) = + em"overloaded alternatives of ${denotStr(alts.head)} with types\n" + + em" ${alts map (_.info)}%\n %" + + def denotStr(denot: Denotation): String = + if (denot.isOverloaded) overloadedAltsStr(denot.alternatives) + else if (denot.symbol.exists) denot.symbol.showLocated + else anonymousTypeMemberStr(denot.info) + + def refStr(tp: Type): String = tp match { + case tp: NamedType => denotStr(tp.denot) + case _ => anonymousTypeMemberStr(tp) + } + + def exprStr(tree: Tree): String = refStr(tree.tpe) + + def patternConstrStr(tree: Tree): String = ??? + + def typeMismatch(tree: Tree, pt: Type, implicitFailure: SearchFailure = NoImplicitMatches): Tree = + errorTree(tree, typeMismatchMsg(normalize(tree.tpe, pt), pt, implicitFailure.postscript)) + + /** A subtype log explaining why `found` does not conform to `expected` */ + def whyNoMatchStr(found: Type, expected: Type) = + if (ctx.settings.explaintypes.value) + "\n" + ctx.typerState.show + "\n" + TypeComparer.explained((found <:< expected)(_)) + else + "" + + def typeMismatchMsg(found: Type, expected: Type, postScript: String = "") = { + // replace constrained polyparams and their typevars by their bounds where possible + object reported extends TypeMap { + def setVariance(v: Int) = variance = v + val constraint = ctx.typerState.constraint + def apply(tp: Type): Type = tp match { + case tp: PolyParam => + constraint.entry(tp) match { + case bounds: TypeBounds => + if (variance < 0) apply(constraint.fullUpperBound(tp)) + else if (variance > 0) apply(constraint.fullLowerBound(tp)) + else tp + case NoType => tp + case instType => apply(instType) + } + case tp: TypeVar => apply(tp.stripTypeVar) + case _ => mapOver(tp) + } + } + val found1 = reported(found) + reported.setVariance(-1) + val expected1 = reported(expected) + TypeMismatch(found1, expected1, whyNoMatchStr(found, expected), postScript) + } + + /** Format `raw` implicitNotFound argument, replacing all + * occurrences of `${X}` where `X` is in `paramNames` with the + * corresponding shown type in `args`. + */ + def implicitNotFoundString(raw: String, paramNames: List[String], args: List[Type]): String = { + def translate(name: String): Option[String] = { + val idx = paramNames.indexOf(name) + if (idx >= 0) Some(quoteReplacement(ex"${args(idx)}")) else None + } + """\$\{\w*\}""".r.replaceSomeIn(raw, m => translate(m.matched.drop(2).init)) + } + } + + def err(implicit ctx: Context): Errors = new Errors +} diff --git a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala new file mode 100644 index 000000000..c390ae808 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala @@ -0,0 +1,191 @@ +package dotty.tools +package dotc +package typer + +import core._ +import ast.{Trees, untpd, tpd, TreeInfo} +import Contexts._ +import Types._ +import Flags._ +import NameOps._ +import Symbols._ +import Decorators._ +import Names._ +import StdNames._ +import Trees._ +import Inferencing._ +import util.Positions._ +import collection.mutable + +object EtaExpansion { + + import tpd._ + + private def lift(defs: mutable.ListBuffer[Tree], expr: Tree, prefix: String = "")(implicit ctx: Context): Tree = + if (isPureExpr(expr)) expr + else { + val name = ctx.freshName(prefix).toTermName + val liftedType = fullyDefinedType(expr.tpe.widen, "lifted expression", expr.pos) + val sym = ctx.newSymbol(ctx.owner, name, EmptyFlags, liftedType, coord = positionCoord(expr.pos)) + defs += ValDef(sym, expr) + ref(sym.valRef) + } + + /** Lift out common part of lhs tree taking part in an operator assignment such as + * + * lhs += expr + */ + def liftAssigned(defs: mutable.ListBuffer[Tree], tree: Tree)(implicit ctx: Context): Tree = tree match { + case Apply(MaybePoly(fn @ Select(pre, name), targs), args) => + cpy.Apply(tree)( + cpy.Select(fn)( + lift(defs, pre), name).appliedToTypeTrees(targs), + liftArgs(defs, fn.tpe, args)) + case Select(pre, name) => + cpy.Select(tree)(lift(defs, pre), name) + case _ => + tree + } + + /** Lift a function argument, stripping any NamedArg wrapper */ + def liftArg(defs: mutable.ListBuffer[Tree], arg: Tree, prefix: String = "")(implicit ctx: Context): Tree = + arg match { + case arg @ NamedArg(name, arg1) => cpy.NamedArg(arg)(name, lift(defs, arg1, prefix)) + case arg => lift(defs, arg, prefix) + } + + /** Lift arguments that are not-idempotent into ValDefs in buffer `defs` + * and replace by the idents of so created ValDefs. + */ + def liftArgs(defs: mutable.ListBuffer[Tree], methRef: Type, args: List[Tree])(implicit ctx: Context) = + methRef.widen match { + case MethodType(paramNames, paramTypes) => + (args, paramNames, paramTypes).zipped map { (arg, name, tp) => + if (tp.isInstanceOf[ExprType]) arg + else liftArg(defs, arg, if (name contains '$') "" else name.toString + "$") + } + case _ => + args map (liftArg(defs, _)) + } + + /** Lift out function prefix and all arguments from application + * + * pre.f(arg1, ..., argN) becomes + * + * val x0 = pre + * val x1 = arg1 + * ... + * val xN = argN + * x0.f(x1, ..., xN) + * + * But leave idempotent expressions alone. + * + */ + def liftApp(defs: mutable.ListBuffer[Tree], tree: Tree)(implicit ctx: Context): Tree = tree match { + case Apply(fn, args) => + cpy.Apply(tree)(liftApp(defs, fn), liftArgs(defs, fn.tpe, args)) + case TypeApply(fn, targs) => + cpy.TypeApply(tree)(liftApp(defs, fn), targs) + case Select(pre, name) if isPureRef(tree) => + cpy.Select(tree)(liftPrefix(defs, pre), name) + case Block(stats, expr) => + liftApp(defs ++= stats, expr) + case New(tpt) => + tree + case _ => + lift(defs, tree) + } + + /** Lift prefix `pre` of an application `pre.f(...)` to + * + * val x0 = pre + * x0.f(...) + * + * unless `pre` is a `New` or `pre` is idempotent. + */ + def liftPrefix(defs: mutable.ListBuffer[Tree], tree: Tree)(implicit ctx: Context): Tree = tree match { + case New(_) => tree + case _ => if (isIdempotentExpr(tree)) tree else lift(defs, tree) + } + + /** Eta-expanding a tree means converting a method reference to a function value. + * @param tree The tree to expand + * @param mt The type of the method reference + * @param xarity The arity of the expected function type + * and assume the lifted application of `tree` (@see liftApp) is + * + * { val xs = es; expr } + * + * If xarity matches the number of parameters in `mt`, the eta-expansion is + * + * { val xs = es; (x1, ..., xn) => expr(x1, ..., xn) } + * + * Note that the function value's parameters are untyped, hence the type will + * be supplied by the environment (or if missing be supplied by the target + * method as a fallback). On the other hand, if `xarity` is different from + * the number of parameters in `mt`, then we cannot propagate parameter types + * from the expected type, and we fallback to using the method's original + * parameter types instead. + * + * In either case, the result is an untyped tree, with `es` and `expr` as typed splices. + */ + def etaExpand(tree: Tree, mt: MethodType, xarity: Int)(implicit ctx: Context): untpd.Tree = { + import untpd._ + assert(!ctx.isAfterTyper) + val defs = new mutable.ListBuffer[tpd.Tree] + val lifted: Tree = TypedSplice(liftApp(defs, tree)) + val paramTypes: List[Tree] = + if (mt.paramTypes.length == xarity) mt.paramTypes map (_ => TypeTree()) + else mt.paramTypes map TypeTree + val params = (mt.paramNames, paramTypes).zipped.map((name, tpe) => + ValDef(name, tpe, EmptyTree).withFlags(Synthetic | Param).withPos(tree.pos)) + var ids: List[Tree] = mt.paramNames map (name => Ident(name).withPos(tree.pos)) + if (mt.paramTypes.nonEmpty && mt.paramTypes.last.isRepeatedParam) + ids = ids.init :+ repeated(ids.last) + var body: Tree = Apply(lifted, ids) + mt.resultType match { + case rt: MethodType if !rt.isImplicit => body = PostfixOp(body, nme.WILDCARD) + case _ => + } + val fn = untpd.Function(params, body) + if (defs.nonEmpty) untpd.Block(defs.toList map (untpd.TypedSplice(_)), fn) else fn + } +} + + /** <p> not needed + * Expand partial function applications of type `type`. + * </p><pre> + * p.f(es_1)...(es_n) + * ==> { + * <b>private synthetic val</b> eta$f = p.f // if p is not stable + * ... + * <b>private synthetic val</b> eta$e_i = e_i // if e_i is not stable + * ... + * (ps_1 => ... => ps_m => eta$f([es_1])...([es_m])(ps_1)...(ps_m)) + * }</pre> + * <p> + * tree is already attributed + * </p> + def etaExpandUntyped(tree: Tree)(implicit ctx: Context): untpd.Tree = { // kept as a reserve for now + def expand(tree: Tree): untpd.Tree = tree.tpe match { + case mt @ MethodType(paramNames, paramTypes) if !mt.isImplicit => + val paramsArgs: List[(untpd.ValDef, untpd.Tree)] = + (paramNames, paramTypes).zipped.map { (name, tp) => + val droppedStarTpe = defn.underlyingOfRepeated(tp) + val param = ValDef( + Modifiers(Param), name, + untpd.TypedSplice(TypeTree(droppedStarTpe)), untpd.EmptyTree) + var arg: untpd.Tree = Ident(name) + if (defn.isRepeatedParam(tp)) + arg = Typed(arg, Ident(tpnme.WILDCARD_STAR)) + (param, arg) + } + val (params, args) = paramsArgs.unzip + untpd.Function(params, Apply(untpd.TypedSplice(tree), args)) + } + + val defs = new mutable.ListBuffer[Tree] + val tree1 = liftApp(defs, tree) + Block(defs.toList map untpd.TypedSplice, expand(tree1)) + } + */ diff --git a/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala b/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala new file mode 100644 index 000000000..c444631ae --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala @@ -0,0 +1,83 @@ +package dotty.tools.dotc +package typer + +import core._ +import Phases._ +import Contexts._ +import Symbols._ +import dotty.tools.dotc.parsing.JavaParsers.JavaParser +import parsing.Parsers.Parser +import config.Config +import config.Printers.{typr, default} +import util.Stats._ +import scala.util.control.NonFatal +import ast.Trees._ + +class FrontEnd extends Phase { + + override def phaseName = "frontend" + override def isTyper = true + import ast.tpd + + def monitor(doing: String)(body: => Unit)(implicit ctx: Context) = + try body + catch { + case NonFatal(ex) => + ctx.echo(s"exception occurred while $doing ${ctx.compilationUnit}") + throw ex + } + + def parse(implicit ctx: Context) = monitor("parsing") { + val unit = ctx.compilationUnit + unit.untpdTree = + if (unit.isJava) new JavaParser(unit.source).parse() + else new Parser(unit.source).parse() + val printer = if (ctx.settings.Xprint.value.contains("parser")) default else typr + printer.println("parsed:\n" + unit.untpdTree.show) + if (Config.checkPositions) + unit.untpdTree.checkPos(nonOverlapping = !unit.isJava && !ctx.reporter.hasErrors) + } + + def enterSyms(implicit ctx: Context) = monitor("indexing") { + val unit = ctx.compilationUnit + ctx.typer.index(unit.untpdTree) + typr.println("entered: " + unit.source) + } + + def typeCheck(implicit ctx: Context) = monitor("typechecking") { + val unit = ctx.compilationUnit + unit.tpdTree = ctx.typer.typedExpr(unit.untpdTree) + typr.println("typed: " + unit.source) + record("retained untyped trees", unit.untpdTree.treeSize) + record("retained typed trees after typer", unit.tpdTree.treeSize) + } + + private def firstTopLevelDef(trees: List[tpd.Tree])(implicit ctx: Context): Symbol = trees match { + case PackageDef(_, defs) :: _ => firstTopLevelDef(defs) + case Import(_, _) :: defs => firstTopLevelDef(defs) + case (tree @ TypeDef(_, _)) :: _ => tree.symbol + case _ => NoSymbol + } + + protected def discardAfterTyper(unit: CompilationUnit)(implicit ctx: Context) = + unit.isJava || firstTopLevelDef(unit.tpdTree :: Nil).isPrimitiveValueClass + + override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = { + val unitContexts = for (unit <- units) yield { + ctx.inform(s"compiling ${unit.source}") + ctx.fresh.setCompilationUnit(unit) + } + unitContexts foreach (parse(_)) + record("parsedTrees", ast.Trees.ntrees) + unitContexts foreach (enterSyms(_)) + unitContexts foreach (typeCheck(_)) + record("total trees after typer", ast.Trees.ntrees) + unitContexts.map(_.compilationUnit).filterNot(discardAfterTyper) + } + + override def run(implicit ctx: Context): Unit = { + parse + enterSyms + typeCheck + } +} diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala new file mode 100644 index 000000000..f3dceea71 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -0,0 +1,844 @@ +package dotty.tools +package dotc +package typer + +import core._ +import ast.{Trees, untpd, tpd, TreeInfo} +import util.Positions._ +import util.Stats.{track, record, monitored} +import printing.Showable +import Contexts._ +import Types._ +import Flags._ +import TypeErasure.{erasure, hasStableErasure} +import Mode.ImplicitsEnabled +import Denotations._ +import NameOps._ +import SymDenotations._ +import Symbols._ +import Types._ +import Decorators._ +import Names._ +import StdNames._ +import Constants._ +import Applications._ +import ProtoTypes._ +import ErrorReporting._ +import Inferencing.fullyDefinedType +import Trees._ +import Hashable._ +import config.Config +import config.Printers.{implicits, implicitsDetailed} +import collection.mutable + +/** Implicit resolution */ +object Implicits { + + /** A common base class of contextual implicits and of-type implicits which + * represents a set of implicit references. + */ + abstract class ImplicitRefs(initctx: Context) { + implicit val ctx: Context = + if (initctx == NoContext) initctx else initctx retractMode Mode.ImplicitsEnabled + + /** The implicit references */ + def refs: List[TermRef] + + /** Return those references in `refs` that are compatible with type `pt`. */ + protected def filterMatching(pt: Type)(implicit ctx: Context): List[TermRef] = track("filterMatching") { + + def refMatches(ref: TermRef)(implicit ctx: Context) = /*ctx.traceIndented(i"refMatches $ref $pt")*/ { + + def discardForView(tpw: Type, argType: Type): Boolean = tpw match { + case mt: MethodType => + mt.isImplicit || + mt.paramTypes.length != 1 || + !(argType relaxed_<:< mt.paramTypes.head)(ctx.fresh.setExploreTyperState) + case poly: PolyType => + // We do not need to call ProtoTypes#constrained on `poly` because + // `refMatches` is always called with mode TypevarsMissContext enabled. + poly.resultType match { + case mt: MethodType => + mt.isImplicit || + mt.paramTypes.length != 1 || + !(argType relaxed_<:< wildApprox(mt.paramTypes.head)(ctx.fresh.setExploreTyperState)) + case rtp => + discardForView(wildApprox(rtp), argType) + } + case tpw: TermRef => + false // can't discard overloaded refs + case tpw => + //if (ctx.typer.isApplicable(tp, argType :: Nil, resultType)) + // println(i"??? $tp is applicable to $this / typeSymbol = ${tpw.typeSymbol}") + !tpw.derivesFrom(defn.FunctionClass(1)) || + ref.symbol == defn.Predef_conforms // + // as an implicit conversion, Predef.$conforms is a no-op, so exclude it + } + + def discardForValueType(tpw: Type): Boolean = tpw match { + case mt: MethodType => !mt.isImplicit + case mt: PolyType => discardForValueType(tpw.resultType) + case _ => false + } + + def discard = pt match { + case pt: ViewProto => discardForView(ref.widen, pt.argType) + case _: ValueTypeOrProto => !defn.isFunctionType(pt) && discardForValueType(ref.widen) + case _ => false + } + + (ref.symbol isAccessibleFrom ref.prefix) && { + if (discard) { + record("discarded eligible") + false + } + else NoViewsAllowed.isCompatible(normalize(ref, pt), pt) + } + } + + if (refs.isEmpty) refs + else refs filter (refMatches(_)(ctx.fresh.addMode(Mode.TypevarsMissContext).setExploreTyperState)) // create a defensive copy of ctx to avoid constraint pollution + } + } + + /** The implicit references coming from the implicit scope of a type. + * @param tp the type determining the implicit scope + * @param companionRefs the companion objects in the implicit scope. + */ + class OfTypeImplicits(tp: Type, val companionRefs: TermRefSet)(initctx: Context) extends ImplicitRefs(initctx) { + assert(initctx.typer != null) + lazy val refs: List[TermRef] = { + val buf = new mutable.ListBuffer[TermRef] + for (companion <- companionRefs) buf ++= companion.implicitMembers + buf.toList + } + + /** The implicit references that are eligible for expected type `tp` */ + lazy val eligible: List[TermRef] = + /*>|>*/ track("eligible in tpe") /*<|<*/ { + /*>|>*/ ctx.traceIndented(i"eligible($tp), companions = ${companionRefs.toList}%, %", implicitsDetailed, show = true) /*<|<*/ { + if (refs.nonEmpty && monitored) record(s"check eligible refs in tpe", refs.length) + filterMatching(tp) + } + } + + override def toString = + i"OfTypeImplicits($tp), companions = ${companionRefs.toList}%, %; refs = $refs%, %." + } + + /** The implicit references coming from the context. + * @param refs the implicit references made visible by the current context. + * Note: The name of the reference might be different from the name of its symbol. + * In the case of a renaming import a => b, the name of the reference is the renamed + * name, b, whereas the name of the symbol is the original name, a. + * @param outerCtx the next outer context that makes visible further implicits + */ + class ContextualImplicits(val refs: List[TermRef], val outerImplicits: ContextualImplicits)(initctx: Context) extends ImplicitRefs(initctx) { + private val eligibleCache = new mutable.AnyRefMap[Type, List[TermRef]] + + /** The implicit references that are eligible for type `tp`. */ + def eligible(tp: Type): List[TermRef] = /*>|>*/ track(s"eligible in ctx") /*<|<*/ { + if (tp.hash == NotCached) computeEligible(tp) + else eligibleCache get tp match { + case Some(eligibles) => + def elided(ci: ContextualImplicits): Int = { + val n = ci.refs.length + if (ci.outerImplicits == NoContext.implicits) n + else n + elided(ci.outerImplicits) + } + if (monitored) record(s"elided eligible refs", elided(this)) + eligibles + case None => + val savedEphemeral = ctx.typerState.ephemeral + ctx.typerState.ephemeral = false + try { + val result = computeEligible(tp) + if (ctx.typerState.ephemeral) record("ephemeral cache miss: eligible") + else eligibleCache(tp) = result + result + } + finally ctx.typerState.ephemeral |= savedEphemeral + } + } + + private def computeEligible(tp: Type): List[TermRef] = /*>|>*/ ctx.traceIndented(i"computeEligible $tp in $refs%, %", implicitsDetailed) /*<|<*/ { + if (monitored) record(s"check eligible refs in ctx", refs.length) + val ownEligible = filterMatching(tp) + if (outerImplicits == NoContext.implicits) ownEligible + else ownEligible ::: { + val shadowed = (ownEligible map (_.name)).toSet + outerImplicits.eligible(tp) filterNot (ref => shadowed contains ref.name) + } + } + + override def toString = { + val own = s"(implicits: ${refs mkString ","})" + if (outerImplicits == NoContext.implicits) own else own + "\n " + outerImplicits + } + + /** This context, or a copy, ensuring root import from symbol `root` + * is not present in outer implicits. + */ + def exclude(root: Symbol): ContextualImplicits = + if (this == NoContext.implicits) this + else { + val outerExcluded = outerImplicits exclude root + if (ctx.importInfo.site.termSymbol == root) outerExcluded + else if (outerExcluded eq outerImplicits) this + else new ContextualImplicits(refs, outerExcluded)(ctx) + } + } + + /** The result of an implicit search */ + abstract class SearchResult + + /** A successful search + * @param ref The implicit reference that succeeded + * @param tree The typed tree that needs to be inserted + * @param ctx The context after the implicit search + */ + case class SearchSuccess(tree: tpd.Tree, ref: TermRef, tstate: TyperState) extends SearchResult { + override def toString = s"SearchSuccess($tree, $ref)" + } + + /** A failed search */ + abstract class SearchFailure extends SearchResult { + /** A note describing the failure in more detail - this + * is either empty or starts with a '\n' + */ + def postscript(implicit ctx: Context): String = "" + } + + /** A "no matching implicit found" failure */ + case object NoImplicitMatches extends SearchFailure + + /** A search failure that can show information about the cause */ + abstract class ExplainedSearchFailure extends SearchFailure { + protected def pt: Type + protected def argument: tpd.Tree + protected def qualify(implicit ctx: Context) = + if (argument.isEmpty) em"match type $pt" + else em"convert from ${argument.tpe} to $pt" + + /** An explanation of the cause of the failure as a string */ + def explanation(implicit ctx: Context): String + } + + /** An ambiguous implicits failure */ + class AmbiguousImplicits(alt1: TermRef, alt2: TermRef, val pt: Type, val argument: tpd.Tree) extends ExplainedSearchFailure { + def explanation(implicit ctx: Context): String = + em"both ${err.refStr(alt1)} and ${err.refStr(alt2)} $qualify" + override def postscript(implicit ctx: Context) = + "\nNote that implicit conversions cannot be applied because they are ambiguous;" + + "\n " + explanation + } + + class NonMatchingImplicit(ref: TermRef, val pt: Type, val argument: tpd.Tree) extends ExplainedSearchFailure { + def explanation(implicit ctx: Context): String = + em"${err.refStr(ref)} does not $qualify" + } + + class ShadowedImplicit(ref: TermRef, shadowing: Type, val pt: Type, val argument: tpd.Tree) extends ExplainedSearchFailure { + def explanation(implicit ctx: Context): String = + em"${err.refStr(ref)} does $qualify but is shadowed by ${err.refStr(shadowing)}" + } + + class DivergingImplicit(ref: TermRef, val pt: Type, val argument: tpd.Tree) extends ExplainedSearchFailure { + def explanation(implicit ctx: Context): String = + em"${err.refStr(ref)} produces a diverging implicit search when trying to $qualify" + } + + class FailedImplicit(failures: List[ExplainedSearchFailure], val pt: Type, val argument: tpd.Tree) extends ExplainedSearchFailure { + def explanation(implicit ctx: Context): String = + if (failures.isEmpty) s" No implicit candidates were found that $qualify" + else " " + (failures map (_.explanation) mkString "\n ") + override def postscript(implicit ctx: Context): String = + i""" + |Implicit search failure summary: + |$explanation""" + } +} + +import Implicits._ + +/** Info relating to implicits that is kept for one run */ +trait ImplicitRunInfo { self: RunInfo => + + private val implicitScopeCache = mutable.AnyRefMap[Type, OfTypeImplicits]() + + /** The implicit scope of a type `tp` + * @param liftingCtx A context to be used when computing the class symbols of + * a type. Types may contain type variables with their instances + * recorded in the current context. To find out the instance of + * a type variable, we need the current context, the current + * runinfo context does not do. + */ + def implicitScope(tp: Type, liftingCtx: Context): OfTypeImplicits = { + + val seen: mutable.Set[Type] = mutable.Set() + + /** Replace every typeref that does not refer to a class by a conjunction of class types + * that has the same implicit scope as the original typeref. The motivation for applying + * this map is that it reduces the total number of types for which we need to + * compute and cache the implicit scope; all variations wrt type parameters or + * abstract types are eliminated. + */ + object liftToClasses extends TypeMap { + override implicit protected val ctx: Context = liftingCtx + override def stopAtStatic = true + def apply(tp: Type) = tp match { + case tp: TypeRef if tp.symbol.isAbstractOrAliasType => + val pre = tp.prefix + def joinClass(tp: Type, cls: ClassSymbol) = + AndType.make(tp, cls.typeRef.asSeenFrom(pre, cls.owner)) + val lead = if (tp.prefix eq NoPrefix) defn.AnyType else apply(tp.prefix) + (lead /: tp.classSymbols)(joinClass) + case tp: TypeVar => + apply(tp.underlying) + case tp: HKApply => + def applyArg(arg: Type) = arg match { + case TypeBounds(lo, hi) => AndType.make(lo, hi) + case _: WildcardType => defn.AnyType + case _ => arg + } + (apply(tp.tycon) /: tp.args)((tc, arg) => AndType.make(tc, applyArg(arg))) + case tp: PolyType => + apply(tp.resType) + case _ => + mapOver(tp) + } + } + + def iscopeRefs(tp: Type): TermRefSet = + if (seen contains tp) EmptyTermRefSet + else { + seen += tp + iscope(tp).companionRefs + } + + // todo: compute implicits directly, without going via companionRefs? + def collectCompanions(tp: Type): TermRefSet = track("computeImplicitScope") { + ctx.traceIndented(i"collectCompanions($tp)", implicits) { + val comps = new TermRefSet + tp match { + case tp: NamedType => + val pre = tp.prefix + comps ++= iscopeRefs(pre) + def addClassScope(cls: ClassSymbol): Unit = { + def addRef(companion: TermRef): Unit = { + val compSym = companion.symbol + if (compSym is Package) + addRef(TermRef.withSig(companion, nme.PACKAGE, Signature.NotAMethod)) + else if (compSym.exists) + comps += companion.asSeenFrom(pre, compSym.owner).asInstanceOf[TermRef] + } + def addParentScope(parent: TypeRef): Unit = { + iscopeRefs(parent) foreach addRef + for (param <- parent.typeParamSymbols) + comps ++= iscopeRefs(tp.member(param.name).info) + } + val companion = cls.companionModule + if (companion.exists) addRef(companion.valRef) + cls.classParents foreach addParentScope + } + tp.classSymbols(liftingCtx) foreach addClassScope + case _ => + // We exclude lower bounds to conform to SLS 7.2: + // "The parts of a type T are: [...] if T is an abstract type, the parts of its upper bound" + for (part <- tp.namedPartsWith(_.isType, excludeLowerBounds = true)) + comps ++= iscopeRefs(part) + } + comps + } + } + + /** The implicit scope of type `tp` + * @param isLifted Type `tp` is the result of a `liftToClasses` application + */ + def iscope(tp: Type, isLifted: Boolean = false): OfTypeImplicits = { + def computeIScope(cacheResult: Boolean) = { + val savedEphemeral = ctx.typerState.ephemeral + ctx.typerState.ephemeral = false + try { + val liftedTp = if (isLifted) tp else liftToClasses(tp) + val refs = + if (liftedTp ne tp) + iscope(liftedTp, isLifted = true).companionRefs + else + collectCompanions(tp) + val result = new OfTypeImplicits(tp, refs)(ctx) + if (ctx.typerState.ephemeral) record("ephemeral cache miss: implicitScope") + else if (cacheResult) implicitScopeCache(tp) = result + result + } + finally ctx.typerState.ephemeral |= savedEphemeral + } + + if (tp.hash == NotCached || !Config.cacheImplicitScopes) + computeIScope(cacheResult = false) + else implicitScopeCache get tp match { + case Some(is) => is + case None => + // Implicit scopes are tricky to cache because of loops. For example + // in `tests/pos/implicit-scope-loop.scala`, the scope of B contains + // the scope of A which contains the scope of B. We break the loop + // by returning EmptyTermRefSet in `collectCompanions` for types + // that we have already seen, but this means that we cannot cache + // the computed scope of A, it is incomplete. + // Keeping track of exactly where these loops happen would require a + // lot of book-keeping, instead we choose to be conservative and only + // cache scopes before any type has been seen. This is unfortunate + // because loops are very common for types in scala.collection. + computeIScope(cacheResult = seen.isEmpty) + } + } + + iscope(tp) + } + + /** A map that counts the number of times an implicit ref was picked */ + val useCount = new mutable.HashMap[TermRef, Int] { + override def default(key: TermRef) = 0 + } + + def clear() = implicitScopeCache.clear() +} + +/** The implicit resolution part of type checking */ +trait Implicits { self: Typer => + + import tpd._ + + override def viewExists(from: Type, to: Type)(implicit ctx: Context): Boolean = ( + !from.isError + && !to.isError + && !ctx.isAfterTyper + && (ctx.mode is Mode.ImplicitsEnabled) + && from.isValueType + && ( from.isValueSubType(to) + || inferView(dummyTreeOfType(from), to) + (ctx.fresh.addMode(Mode.ImplicitExploration).setExploreTyperState) + .isInstanceOf[SearchSuccess] + ) + ) + + /** Find an implicit conversion to apply to given tree `from` so that the + * result is compatible with type `to`. + */ + def inferView(from: Tree, to: Type)(implicit ctx: Context): SearchResult = track("inferView") { + if ( (to isRef defn.AnyClass) + || (to isRef defn.ObjectClass) + || (to isRef defn.UnitClass) + || (from.tpe isRef defn.NothingClass) + || (from.tpe isRef defn.NullClass) + || (from.tpe eq NoPrefix)) NoImplicitMatches + else + try inferImplicit(to.stripTypeVar.widenExpr, from, from.pos) + catch { + case ex: AssertionError => + implicits.println(s"view $from ==> $to") + implicits.println(ctx.typerState.constraint.show) + implicits.println(TypeComparer.explained(implicit ctx => from.tpe <:< to)) + throw ex + } + } + + /** Find an implicit argument for parameter `formal`. + * @param error An error handler that gets an error message parameter + * which is itself parameterized by another string, + * indicating where the implicit parameter is needed + */ + def inferImplicitArg(formal: Type, error: (String => String) => Unit, pos: Position)(implicit ctx: Context): Tree = + inferImplicit(formal, EmptyTree, pos) match { + case SearchSuccess(arg, _, _) => + arg + case ambi: AmbiguousImplicits => + error(where => s"ambiguous implicits: ${ambi.explanation} of $where") + EmptyTree + case failure: SearchFailure => + val arg = synthesizedClassTag(formal, pos) + if (!arg.isEmpty) arg + else { + var msgFn = (where: String) => + em"no implicit argument of type $formal found for $where" + failure.postscript + for { + notFound <- formal.typeSymbol.getAnnotation(defn.ImplicitNotFoundAnnot) + Trees.Literal(Constant(raw: String)) <- notFound.argument(0) + } { + msgFn = where => + err.implicitNotFoundString( + raw, + formal.typeSymbol.typeParams.map(_.name.unexpandedName.toString), + formal.argInfos) + } + error(msgFn) + EmptyTree + } + } + + /** If `formal` is of the form ClassTag[T], where `T` is a class type, + * synthesize a class tag for `T`. + */ + def synthesizedClassTag(formal: Type, pos: Position)(implicit ctx: Context): Tree = { + if (formal.isRef(defn.ClassTagClass)) + formal.argTypes match { + case arg :: Nil => + val tp = fullyDefinedType(arg, "ClassTag argument", pos) + if (hasStableErasure(tp)) + return ref(defn.ClassTagModule) + .select(nme.apply) + .appliedToType(tp) + .appliedTo(clsOf(erasure(tp))) + .withPos(pos) + case _ => + } + EmptyTree + } + + private def assumedCanEqual(ltp: Type, rtp: Type)(implicit ctx: Context) = { + val lift = new TypeMap { + def apply(t: Type) = t match { + case t: TypeRef => + t.info match { + case TypeBounds(lo, hi) if lo ne hi => hi + case _ => t + } + case _ => + if (variance > 0) mapOver(t) else t + } + } + ltp.isError || rtp.isError || ltp <:< lift(rtp) || rtp <:< lift(ltp) + } + + /** Check that equality tests between types `ltp` and `rtp` make sense */ + def checkCanEqual(ltp: Type, rtp: Type, pos: Position)(implicit ctx: Context): Unit = + if (!ctx.isAfterTyper && !assumedCanEqual(ltp, rtp)) { + val res = inferImplicitArg( + defn.EqType.appliedTo(ltp, rtp), msgFun => ctx.error(msgFun(""), pos), pos) + implicits.println(i"Eq witness found: $res: ${res.tpe}") + } + + /** Find an implicit parameter or conversion. + * @param pt The expected type of the parameter or conversion. + * @param argument If an implicit conversion is searched, the argument to which + * it should be applied, EmptyTree otherwise. + * @param pos The position where errors should be reported. + * !!! todo: catch potential cycles + */ + def inferImplicit(pt: Type, argument: Tree, pos: Position)(implicit ctx: Context): SearchResult = track("inferImplicit") { + assert(!ctx.isAfterTyper, + if (argument.isEmpty) i"missing implicit parameter of type $pt after typer" + else i"type error: ${argument.tpe} does not conform to $pt${err.whyNoMatchStr(argument.tpe, pt)}") + val prevConstr = ctx.typerState.constraint + ctx.traceIndented(s"search implicit ${pt.show}, arg = ${argument.show}: ${argument.tpe.show}", implicits, show = true) { + assert(!pt.isInstanceOf[ExprType]) + val isearch = + if (ctx.settings.explaintypes.value) new ExplainedImplicitSearch(pt, argument, pos) + else new ImplicitSearch(pt, argument, pos) + val result = isearch.bestImplicit + result match { + case result: SearchSuccess => + result.tstate.commit() + result + case result: AmbiguousImplicits => + val deepPt = pt.deepenProto + if (deepPt ne pt) inferImplicit(deepPt, argument, pos) + else if (ctx.scala2Mode && !ctx.mode.is(Mode.OldOverloadingResolution)) { + inferImplicit(pt, argument, pos)(ctx.addMode(Mode.OldOverloadingResolution)) match { + case altResult: SearchSuccess => + ctx.migrationWarning( + s"According to new implicit resolution rules, this will be ambiguous:\n ${result.explanation}", + pos) + altResult + case _ => + result + } + } + else result + case _ => + assert(prevConstr eq ctx.typerState.constraint) + result + } + } + } + + /** An implicit search; parameters as in `inferImplicit` */ + class ImplicitSearch(protected val pt: Type, protected val argument: Tree, pos: Position)(implicit ctx: Context) { + + private def nestedContext = ctx.fresh.setMode(ctx.mode &~ Mode.ImplicitsEnabled) + + private def implicitProto(resultType: Type, f: Type => Type) = + if (argument.isEmpty) f(resultType) else ViewProto(f(argument.tpe.widen), f(resultType)) + // Not clear whether we need to drop the `.widen` here. All tests pass with it in place, though. + + assert(argument.isEmpty || argument.tpe.isValueType || argument.tpe.isInstanceOf[ExprType], + em"found: $argument: ${argument.tpe}, expected: $pt") + + /** The expected type for the searched implicit */ + lazy val fullProto = implicitProto(pt, identity) + + lazy val funProto = fullProto match { + case proto: ViewProto => + FunProto(untpd.TypedSplice(dummyTreeOfType(proto.argType)) :: Nil, proto.resultType, self) + case proto => proto + } + + /** The expected type where parameters and uninstantiated typevars are replaced by wildcard types */ + val wildProto = implicitProto(pt, wildApprox(_)) + + /** Search failures; overridden in ExplainedImplicitSearch */ + protected def nonMatchingImplicit(ref: TermRef): SearchFailure = NoImplicitMatches + protected def divergingImplicit(ref: TermRef): SearchFailure = NoImplicitMatches + protected def shadowedImplicit(ref: TermRef, shadowing: Type): SearchFailure = NoImplicitMatches + protected def failedSearch: SearchFailure = NoImplicitMatches + + /** Search a list of eligible implicit references */ + def searchImplicits(eligible: List[TermRef], contextual: Boolean): SearchResult = { + val constr = ctx.typerState.constraint + + /** Try to typecheck an implicit reference */ + def typedImplicit(ref: TermRef)(implicit ctx: Context): SearchResult = track("typedImplicit") { ctx.traceIndented(i"typed implicit $ref, pt = $pt, implicitsEnabled == ${ctx.mode is ImplicitsEnabled}", implicits, show = true) { + assert(constr eq ctx.typerState.constraint) + var generated: Tree = tpd.ref(ref).withPos(pos) + if (!argument.isEmpty) + generated = typedUnadapted( + untpd.Apply(untpd.TypedSplice(generated), untpd.TypedSplice(argument) :: Nil), + pt) + val generated1 = adapt(generated, pt) + lazy val shadowing = + typed(untpd.Ident(ref.name) withPos pos.toSynthetic, funProto)( + nestedContext.addMode(Mode.ImplicitShadowing).setExploreTyperState) + def refMatches(shadowing: Tree): Boolean = + ref.symbol == closureBody(shadowing).symbol || { + shadowing match { + case Trees.Select(qual, nme.apply) => refMatches(qual) + case _ => false + } + } + // Does there exist an implicit value of type `Eq[tp, tp]`? + def hasEq(tp: Type): Boolean = + new ImplicitSearch(defn.EqType.appliedTo(tp, tp), EmptyTree, pos).bestImplicit match { + case result: SearchSuccess => result.ref.symbol != defn.Predef_eqAny + case result: AmbiguousImplicits => true + case _ => false + } + def validEqAnyArgs(tp1: Type, tp2: Type) = { + List(tp1, tp2).foreach(fullyDefinedType(_, "eqAny argument", pos)) + assumedCanEqual(tp1, tp2) || !hasEq(tp1) && !hasEq(tp2) || + { implicits.println(i"invalid eqAny[$tp1, $tp2]"); false } + } + if (ctx.reporter.hasErrors) + nonMatchingImplicit(ref) + else if (contextual && !ctx.mode.is(Mode.ImplicitShadowing) && + !shadowing.tpe.isError && !refMatches(shadowing)) { + implicits.println(i"SHADOWING $ref in ${ref.termSymbol.owner} is shadowed by $shadowing in ${shadowing.symbol.owner}") + shadowedImplicit(ref, methPart(shadowing).tpe) + } + else generated1 match { + case TypeApply(fn, targs @ (arg1 :: arg2 :: Nil)) + if fn.symbol == defn.Predef_eqAny && !validEqAnyArgs(arg1.tpe, arg2.tpe) => + nonMatchingImplicit(ref) + case _ => + SearchSuccess(generated1, ref, ctx.typerState) + } + }} + + /** Given a list of implicit references, produce a list of all implicit search successes, + * where the first is supposed to be the best one. + * @param pending The list of implicit references that remain to be investigated + * @param acc An accumulator of successful matches found so far. + */ + def rankImplicits(pending: List[TermRef], acc: List[SearchSuccess]): List[SearchSuccess] = pending match { + case ref :: pending1 => + val history = ctx.searchHistory nest wildProto + val result = + if (history eq ctx.searchHistory) divergingImplicit(ref) + else typedImplicit(ref)(nestedContext.setNewTyperState.setSearchHistory(history)) + result match { + case fail: SearchFailure => + rankImplicits(pending1, acc) + case best: SearchSuccess => + if (ctx.mode.is(Mode.ImplicitExploration)) best :: Nil + else { + val newPending = pending1 filter (isAsGood(_, best.ref)(nestedContext.setExploreTyperState)) + rankImplicits(newPending, best :: acc) + } + } + case nil => acc + } + + /** If the (result types of) the expected type, and both alternatives + * are all numeric value types, return the alternative which has + * the smaller numeric subtype as result type, if it exists. + * (This alternative is then discarded). + */ + def numericValueTieBreak(alt1: SearchSuccess, alt2: SearchSuccess): SearchResult = { + def isNumeric(tp: Type) = tp.typeSymbol.isNumericValueClass + def isProperSubType(tp1: Type, tp2: Type) = + tp1.isValueSubType(tp2) && !tp2.isValueSubType(tp1) + val rpt = pt.resultType + val rt1 = alt1.ref.widen.resultType + val rt2 = alt2.ref.widen.resultType + if (isNumeric(rpt) && isNumeric(rt1) && isNumeric(rt2)) + if (isProperSubType(rt1, rt2)) alt1 + else if (isProperSubType(rt2, rt1)) alt2 + else NoImplicitMatches + else NoImplicitMatches + } + + /** Convert a (possibly empty) list of search successes into a single search result */ + def condense(hits: List[SearchSuccess]): SearchResult = hits match { + case best :: alts => + alts find (alt => isAsGood(alt.ref, best.ref)(ctx.fresh.setExploreTyperState)) match { + case Some(alt) => + /* !!! DEBUG + println(i"ambiguous refs: ${hits map (_.ref) map (_.show) mkString ", "}") + isAsGood(best.ref, alt.ref, explain = true)(ctx.fresh.withExploreTyperState) + */ + numericValueTieBreak(best, alt) match { + case eliminated: SearchSuccess => condense(hits.filter(_ ne eliminated)) + case _ => new AmbiguousImplicits(best.ref, alt.ref, pt, argument) + } + case None => + ctx.runInfo.useCount(best.ref) += 1 + best + } + case Nil => + failedSearch + } + + /** Sort list of implicit references according to their popularity + * (# of times each was picked in current run). + */ + def sort(eligible: List[TermRef]) = eligible match { + case Nil => eligible + case e1 :: Nil => eligible + case e1 :: e2 :: Nil => + if (ctx.runInfo.useCount(e1) < ctx.runInfo.useCount(e2)) e2 :: e1 :: Nil + else eligible + case _ => eligible.sortBy(-ctx.runInfo.useCount(_)) + } + + condense(rankImplicits(sort(eligible), Nil)) + } + + /** Find a unique best implicit reference */ + def bestImplicit: SearchResult = { + searchImplicits(ctx.implicits.eligible(wildProto), contextual = true) match { + case result: SearchSuccess => result + case result: AmbiguousImplicits => result + case result: SearchFailure => + searchImplicits(implicitScope(wildProto).eligible, contextual = false) + } + } + + def implicitScope(tp: Type): OfTypeImplicits = ctx.runInfo.implicitScope(tp, ctx) + } + + final class ExplainedImplicitSearch(pt: Type, argument: Tree, pos: Position)(implicit ctx: Context) + extends ImplicitSearch(pt, argument, pos) { + private var myFailures = new mutable.ListBuffer[ExplainedSearchFailure] + private def record(fail: ExplainedSearchFailure) = { + myFailures += fail + fail + } + def failures = myFailures.toList + override def nonMatchingImplicit(ref: TermRef) = + record(new NonMatchingImplicit(ref, pt, argument)) + override def divergingImplicit(ref: TermRef) = + record(new DivergingImplicit(ref, pt, argument)) + override def shadowedImplicit(ref: TermRef, shadowing: Type): SearchFailure = + record(new ShadowedImplicit(ref, shadowing, pt, argument)) + override def failedSearch: SearchFailure = { + //println(s"wildProto = $wildProto") + //println(s"implicit scope = ${implicitScope(wildProto).companionRefs}") + new FailedImplicit(failures, pt, argument) + } + } +} + +/** Records the history of currently open implicit searches + * @param searchDepth The number of open searches. + * @param seen A map that records for each class symbol of a type + * that's currently searched for the complexity of the + * type that is searched for (wrt `typeSize`). The map + * is populated only once `searchDepth` is greater than + * the threshold given in the `XminImplicitSearchDepth` setting. + */ +class SearchHistory(val searchDepth: Int, val seen: Map[ClassSymbol, Int]) { + + /** The number of RefinementTypes in this type, after all aliases are expanded */ + private def typeSize(tp: Type)(implicit ctx: Context): Int = { + val accu = new TypeAccumulator[Int] { + def apply(n: Int, tp: Type): Int = tp match { + case tp: RefinedType => + foldOver(n + 1, tp) + case tp: TypeRef if tp.info.isAlias => + apply(n, tp.superType) + case _ => + foldOver(n, tp) + } + } + accu.apply(0, tp) + } + + /** Check for possible divergence. If one is detected return the current search history + * (this will be used as a criterion to abandon the implicit search in rankImplicits). + * If no divergence is detected, produce a new search history nested in the current one + * which records that we are now also looking for type `proto`. + * + * As long as `searchDepth` is lower than the `XminImplicitSearchDepth` value + * in settings, a new history is always produced, so the implicit search is always + * undertaken. If `searchDepth` matches or exceeds the `XminImplicitSearchDepth` value, + * we test that the new search is for a class that is either not yet in the set of + * `seen` classes, or the complexity of the type `proto` being searched for is strictly + * lower than the complexity of the type that was previously encountered and that had + * the same class symbol as `proto`. A possible divergence is detected if that test fails. + */ + def nest(proto: Type)(implicit ctx: Context): SearchHistory = { + if (searchDepth < ctx.settings.XminImplicitSearchDepth.value) + new SearchHistory(searchDepth + 1, seen) + else { + val size = typeSize(proto) + def updateMap(csyms: List[ClassSymbol], seen: Map[ClassSymbol, Int]): SearchHistory = csyms match { + case csym :: csyms1 => + seen get csym match { + // proto complexity is >= than the last time it was seen → diverge + case Some(prevSize) if size >= prevSize => this + case _ => updateMap(csyms1, seen.updated(csym, size)) + } + case _ => + new SearchHistory(searchDepth + 1, seen) + } + if (proto.classSymbols.isEmpty) this + else updateMap(proto.classSymbols, seen) + } + } +} + +/** A set of term references where equality is =:= */ +class TermRefSet(implicit ctx: Context) extends mutable.Traversable[TermRef] { + import collection.JavaConverters._ + private val elems = (new java.util.LinkedHashMap[TermSymbol, List[Type]]).asScala + + def += (ref: TermRef): Unit = { + val pre = ref.prefix + val sym = ref.symbol.asTerm + elems get sym match { + case Some(prefixes) => + if (!(prefixes exists (_ =:= pre))) elems(sym) = pre :: prefixes + case None => + elems(sym) = pre :: Nil + } + } + + def ++= (refs: TraversableOnce[TermRef]): Unit = + refs foreach += + + override def foreach[U](f: TermRef => U): Unit = + for (sym <- elems.keysIterator) + for (pre <- elems(sym)) + f(TermRef(pre, sym)) +} + +@sharable object EmptyTermRefSet extends TermRefSet()(NoContext) diff --git a/compiler/src/dotty/tools/dotc/typer/ImportInfo.scala b/compiler/src/dotty/tools/dotc/typer/ImportInfo.scala new file mode 100644 index 000000000..3aa289181 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/ImportInfo.scala @@ -0,0 +1,117 @@ +package dotty.tools +package dotc +package typer + +import ast.{tpd, untpd} +import ast.Trees._ +import core._ +import util.SimpleMap +import Symbols._, Names._, Denotations._, Types._, Contexts._, StdNames._, Flags._ +import Decorators.StringInterpolators + +object ImportInfo { + /** The import info for a root import from given symbol `sym` */ + def rootImport(refFn: () => TermRef)(implicit ctx: Context) = { + val selectors = untpd.Ident(nme.WILDCARD) :: Nil + def expr = tpd.Ident(refFn()) + def imp = tpd.Import(expr, selectors) + new ImportInfo(imp.symbol, selectors, isRootImport = true) + } +} + +/** Info relating to an import clause + * @param sym The import symbol defined by the clause + * @param selectors The selector clauses + * @param rootImport true if this is one of the implicit imports of scala, java.lang + * or Predef in the start context, false otherwise. + */ +class ImportInfo(symf: => Symbol, val selectors: List[untpd.Tree], val isRootImport: Boolean = false)(implicit ctx: Context) { + + lazy val sym = symf + + /** The (TermRef) type of the qualifier of the import clause */ + def site(implicit ctx: Context): Type = { + val ImportType(expr) = sym.info + expr.tpe + } + + /** The names that are excluded from any wildcard import */ + def excluded: Set[TermName] = { ensureInitialized(); myExcluded } + + /** A mapping from renamed to original names */ + def reverseMapping: SimpleMap[TermName, TermName] = { ensureInitialized(); myMapped } + + /** The original names imported by-name before renaming */ + def originals: Set[TermName] = { ensureInitialized(); myOriginals } + + /** Does the import clause end with wildcard? */ + def isWildcardImport = { ensureInitialized(); myWildcardImport } + + private var myExcluded: Set[TermName] = null + private var myMapped: SimpleMap[TermName, TermName] = null + private var myOriginals: Set[TermName] = null + private var myWildcardImport: Boolean = false + + /** Compute info relating to the selector list */ + private def ensureInitialized(): Unit = if (myExcluded == null) { + myExcluded = Set() + myMapped = SimpleMap.Empty + myOriginals = Set() + def recur(sels: List[untpd.Tree]): Unit = sels match { + case sel :: sels1 => + sel match { + case Thicket(Ident(name: TermName) :: Ident(nme.WILDCARD) :: Nil) => + myExcluded += name + case Thicket(Ident(from: TermName) :: Ident(to: TermName) :: Nil) => + myMapped = myMapped.updated(to, from) + myExcluded += from + myOriginals += from + case Ident(nme.WILDCARD) => + myWildcardImport = true + case Ident(name: TermName) => + myMapped = myMapped.updated(name, name) + myOriginals += name + } + recur(sels1) + case nil => + } + recur(selectors) + } + + /** The implicit references imported by this import clause */ + def importedImplicits: List[TermRef] = { + val pre = site + if (isWildcardImport) { + val refs = pre.implicitMembers + if (excluded.isEmpty) refs + else refs filterNot (ref => excluded contains ref.name.toTermName) + } else + for { + renamed <- reverseMapping.keys + denot <- pre.member(reverseMapping(renamed)).altsWith(_ is Implicit) + } yield TermRef.withSigAndDenot(pre, renamed, denot.signature, denot) + } + + /** The root import symbol hidden by this symbol, or NoSymbol if no such symbol is hidden. + * Note: this computation needs to work even for un-initialized import infos, and + * is not allowed to force initialization. + */ + lazy val hiddenRoot: Symbol = { + val sym = site.termSymbol + def hasMaskingSelector = selectors exists { + case Thicket(_ :: Ident(nme.WILDCARD) :: Nil) => true + case _ => false + } + if ((defn.RootImportTypes exists (_.symbol == sym)) && hasMaskingSelector) sym else NoSymbol + } + + override def toString = { + val siteStr = site.show + val exprStr = if (siteStr endsWith ".type") siteStr dropRight 5 else siteStr + val selectorStr = selectors match { + case Ident(name) :: Nil => name.show + case _ => "{...}" + } + i"import $exprStr.$selectorStr" + } +} diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala new file mode 100644 index 000000000..aede4974a --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -0,0 +1,362 @@ +package dotty.tools +package dotc +package typer + +import core._ +import ast._ +import Contexts._, Types._, Flags._, Denotations._, Names._, StdNames._, NameOps._, Symbols._ +import Trees._ +import Constants._ +import Scopes._ +import ProtoTypes._ +import annotation.unchecked +import util.Positions._ +import util.{Stats, SimpleMap} +import util.common._ +import Decorators._ +import Uniques._ +import config.Printers.{typr, constr} +import annotation.tailrec +import reporting._ +import collection.mutable + +object Inferencing { + + import tpd._ + + /** Is type fully defined, meaning the type does not contain wildcard types + * or uninstantiated type variables. As a side effect, this will minimize + * any uninstantiated type variables, according to the given force degree, + * but only if the overall result of `isFullyDefined` is `true`. + * Variables that are successfully minimized do not count as uninstantiated. + */ + def isFullyDefined(tp: Type, force: ForceDegree.Value)(implicit ctx: Context): Boolean = { + val nestedCtx = ctx.fresh.setNewTyperState + val result = new IsFullyDefinedAccumulator(force)(nestedCtx).process(tp) + if (result) nestedCtx.typerState.commit() + result + } + + /** The fully defined type, where all type variables are forced. + * Throws an error if type contains wildcards. + */ + def fullyDefinedType(tp: Type, what: String, pos: Position)(implicit ctx: Context) = + if (isFullyDefined(tp, ForceDegree.all)) tp + else throw new Error(i"internal error: type of $what $tp is not fully defined, pos = $pos") // !!! DEBUG + + + /** Instantiate selected type variables `tvars` in type `tp` */ + def instantiateSelected(tp: Type, tvars: List[Type])(implicit ctx: Context): Unit = + new IsFullyDefinedAccumulator(new ForceDegree.Value(tvars.contains, minimizeAll = true)).process(tp) + + /** The accumulator which forces type variables using the policy encoded in `force` + * and returns whether the type is fully defined. The direction in which + * a type variable is instantiated is determined as follows: + * 1. T is minimized if the constraint over T is only from below (i.e. + * constrained lower bound != given lower bound and + * constrained upper bound == given upper bound). + * 2. T is maximized if the constraint over T is only from above (i.e. + * constrained upper bound != given upper bound and + * constrained lower bound == given lower bound). + * If (1) and (2) do not apply: + * 3. T is maximized if it appears only contravariantly in the given type. + * 4. T is minimized in all other cases. + * + * The instantiation is done in two phases: + * 1st Phase: Try to instantiate minimizable type variables to + * their lower bound. Record whether successful. + * 2nd Phase: If first phase was successful, instantiate all remaining type variables + * to their upper bound. + */ + private class IsFullyDefinedAccumulator(force: ForceDegree.Value)(implicit ctx: Context) extends TypeAccumulator[Boolean] { + private def instantiate(tvar: TypeVar, fromBelow: Boolean): Type = { + val inst = tvar.instantiate(fromBelow) + typr.println(i"forced instantiation of ${tvar.origin} = $inst") + inst + } + private var toMaximize: Boolean = false + def apply(x: Boolean, tp: Type): Boolean = tp.dealias match { + case _: WildcardType | _: ProtoType => + false + case tvar: TypeVar + if !tvar.isInstantiated && ctx.typerState.constraint.contains(tvar) => + force.appliesTo(tvar) && { + val direction = instDirection(tvar.origin) + if (direction != 0) { + //if (direction > 0) println(s"inst $tvar dir = up") + instantiate(tvar, direction < 0) + } + else { + val minimize = + force.minimizeAll || + variance >= 0 && !( + force == ForceDegree.noBottom && + defn.isBottomType(ctx.typeComparer.approximation(tvar.origin, fromBelow = true))) + if (minimize) instantiate(tvar, fromBelow = true) + else toMaximize = true + } + foldOver(x, tvar) + } + case tp => + foldOver(x, tp) + } + + private class UpperInstantiator(implicit ctx: Context) extends TypeAccumulator[Unit] { + def apply(x: Unit, tp: Type): Unit = { + tp match { + case tvar: TypeVar if !tvar.isInstantiated => + instantiate(tvar, fromBelow = false) + case _ => + } + foldOver(x, tp) + } + } + + def process(tp: Type): Boolean = { + val res = apply(true, tp) + if (res && toMaximize) new UpperInstantiator().apply((), tp) + res + } + } + + /** The list of uninstantiated type variables bound by some prefix of type `T` which + * occur in at least one formal parameter type of a prefix application. + * Considered prefixes are: + * - The function `f` of an application node `f(e1, .., en)` + * - The function `f` of a type application node `f[T1, ..., Tn]` + * - The prefix `p` of a selection `p.f`. + * - The result expression `e` of a block `{s1; .. sn; e}`. + */ + def tvarsInParams(tree: Tree)(implicit ctx: Context): List[TypeVar] = { + @tailrec def boundVars(tree: Tree, acc: List[TypeVar]): List[TypeVar] = tree match { + case Apply(fn, _) => boundVars(fn, acc) + case TypeApply(fn, targs) => + val tvars = targs.tpes.collect { + case tvar: TypeVar if !tvar.isInstantiated => tvar + } + boundVars(fn, acc ::: tvars) + case Select(pre, _) => boundVars(pre, acc) + case Block(_, expr) => boundVars(expr, acc) + case _ => acc + } + @tailrec def occurring(tree: Tree, toTest: List[TypeVar], acc: List[TypeVar]): List[TypeVar] = + if (toTest.isEmpty) acc + else tree match { + case Apply(fn, _) => + fn.tpe.widen match { + case mtp: MethodType => + val (occ, nocc) = toTest.partition(tvar => mtp.paramTypes.exists(tvar.occursIn)) + occurring(fn, nocc, occ ::: acc) + case _ => + occurring(fn, toTest, acc) + } + case TypeApply(fn, targs) => occurring(fn, toTest, acc) + case Select(pre, _) => occurring(pre, toTest, acc) + case Block(_, expr) => occurring(expr, toTest, acc) + case _ => acc + } + occurring(tree, boundVars(tree, Nil), Nil) + } + + /** The instantiation direction for given poly param computed + * from the constraint: + * @return 1 (maximize) if constraint is uniformly from above, + * -1 (minimize) if constraint is uniformly from below, + * 0 if unconstrained, or constraint is from below and above. + */ + private def instDirection(param: PolyParam)(implicit ctx: Context): Int = { + val constrained = ctx.typerState.constraint.fullBounds(param) + val original = param.binder.paramBounds(param.paramNum) + val cmp = ctx.typeComparer + val approxBelow = + if (!cmp.isSubTypeWhenFrozen(constrained.lo, original.lo)) 1 else 0 + val approxAbove = + if (!cmp.isSubTypeWhenFrozen(original.hi, constrained.hi)) 1 else 0 + approxAbove - approxBelow + } + + /** Recursively widen and also follow type declarations and type aliases. */ + def widenForMatchSelector(tp: Type)(implicit ctx: Context): Type = tp.widen match { + case tp: TypeRef if !tp.symbol.isClass => + widenForMatchSelector(tp.superType) + case tp: HKApply => + widenForMatchSelector(tp.superType) + case tp: AnnotatedType => + tp.derivedAnnotatedType(widenForMatchSelector(tp.tpe), tp.annot) + case tp => tp + } + + /** Following type aliases and stripping refinements and annotations, if one arrives at a + * class type reference where the class has a companion module, a reference to + * that companion module. Otherwise NoType + */ + def companionRef(tp: Type)(implicit ctx: Context): Type = + tp.underlyingClassRef(refinementOK = true) match { + case tp: TypeRef => + val companion = tp.classSymbol.companionModule + if (companion.exists) + companion.valRef.asSeenFrom(tp.prefix, companion.symbol.owner) + else NoType + case _ => NoType + } + + /** Interpolate those undetermined type variables in the widened type of this tree + * which are introduced by type application contained in the tree. + * If such a variable appears covariantly in type `tp` or does not appear at all, + * approximate it by its lower bound. Otherwise, if it appears contravariantly + * in type `tp` approximate it by its upper bound. + * @param ownedBy if it is different from NoSymbol, all type variables owned by + * `ownedBy` qualify, independent of position. + * Without that second condition, it can be that certain variables escape + * interpolation, for instance when their tree was eta-lifted, so + * the typechecked tree is no longer the tree in which the variable + * was declared. A concrete example of this phenomenon can be + * observed when compiling core.TypeOps#asSeenFrom. + */ + def interpolateUndetVars(tree: Tree, ownedBy: Symbol)(implicit ctx: Context): Unit = { + val constraint = ctx.typerState.constraint + val qualifies = (tvar: TypeVar) => + (tree contains tvar.owningTree) || ownedBy.exists && tvar.owner == ownedBy + def interpolate() = Stats.track("interpolateUndetVars") { + val tp = tree.tpe.widen + constr.println(s"interpolate undet vars in ${tp.show}, pos = ${tree.pos}, mode = ${ctx.mode}, undets = ${constraint.uninstVars map (tvar => s"${tvar.show}@${tvar.owningTree.pos}")}") + constr.println(s"qualifying undet vars: ${constraint.uninstVars filter qualifies map (tvar => s"$tvar / ${tvar.show}")}, constraint: ${constraint.show}") + + val vs = variances(tp, qualifies) + val hasUnreportedErrors = ctx.typerState.reporter match { + case r: StoreReporter if r.hasErrors => true + case _ => false + } + // Avoid interpolating variables if typerstate has unreported errors. + // Reason: The errors might reflect unsatisfiable constraints. In that + // case interpolating without taking account the constraints risks producing + // nonsensical types that then in turn produce incomprehensible errors. + // An example is in neg/i1240.scala. Without the condition in the next code line + // we get for + // + // val y: List[List[String]] = List(List(1)) + // + // i1430.scala:5: error: type mismatch: + // found : Int(1) + // required: Nothing + // val y: List[List[String]] = List(List(1)) + // ^ + // With the condition, we get the much more sensical: + // + // i1430.scala:5: error: type mismatch: + // found : Int(1) + // required: String + // val y: List[List[String]] = List(List(1)) + if (!hasUnreportedErrors) + vs foreachBinding { (tvar, v) => + if (v != 0) { + typr.println(s"interpolate ${if (v == 1) "co" else "contra"}variant ${tvar.show} in ${tp.show}") + tvar.instantiate(fromBelow = v == 1) + } + } + for (tvar <- constraint.uninstVars) + if (!(vs contains tvar) && qualifies(tvar)) { + typr.println(s"instantiating non-occurring ${tvar.show} in ${tp.show} / $tp") + tvar.instantiate(fromBelow = true) + } + } + if (constraint.uninstVars exists qualifies) interpolate() + } + + /** Instantiate undetermined type variables to that type `tp` is + * maximized and return None. If this is not possible, because a non-variant + * typevar is not uniquely determined, return that typevar in a Some. + */ + def maximizeType(tp: Type)(implicit ctx: Context): Option[TypeVar] = Stats.track("maximizeType") { + val vs = variances(tp, alwaysTrue) + var result: Option[TypeVar] = None + vs foreachBinding { (tvar, v) => + if (v == 1) tvar.instantiate(fromBelow = false) + else if (v == -1) tvar.instantiate(fromBelow = true) + else { + val bounds = ctx.typerState.constraint.fullBounds(tvar.origin) + if (!(bounds.hi <:< bounds.lo)) result = Some(tvar) + tvar.instantiate(fromBelow = false) + } + } + result + } + + type VarianceMap = SimpleMap[TypeVar, Integer] + + /** All occurrences of type vars in this type that satisfy predicate + * `include` mapped to their variances (-1/0/1) in this type, where + * -1 means: only covariant occurrences + * +1 means: only covariant occurrences + * 0 means: mixed or non-variant occurrences + * + * Note: We intentionally use a relaxed version of variance here, + * where the variance does not change under a prefix of a named type + * (the strict version makes prefixes invariant). This turns out to be + * better for type inference. In a nutshell, if a type variable occurs + * like this: + * + * (U? >: x.type) # T + * + * we want to instantiate U to x.type right away. No need to wait further. + */ + private def variances(tp: Type, include: TypeVar => Boolean)(implicit ctx: Context): VarianceMap = Stats.track("variances") { + val constraint = ctx.typerState.constraint + + object accu extends TypeAccumulator[VarianceMap] { + def setVariance(v: Int) = variance = v + def apply(vmap: VarianceMap, t: Type): VarianceMap = t match { + case t: TypeVar + if !t.isInstantiated && (ctx.typerState.constraint contains t) && include(t) => + val v = vmap(t) + if (v == null) vmap.updated(t, variance) + else if (v == variance || v == 0) vmap + else vmap.updated(t, 0) + case _ => + foldOver(vmap, t) + } + override def applyToPrefix(vmap: VarianceMap, t: NamedType) = + apply(vmap, t.prefix) + } + + /** Include in `vmap` type variables occurring in the constraints of type variables + * already in `vmap`. Specifically: + * - if `tvar` is covariant in `vmap`, include all variables in its lower bound + * (because they influence the minimal solution of `tvar`), + * - if `tvar` is contravariant in `vmap`, include all variables in its upper bound + * at flipped variances (because they influence the maximal solution of `tvar`), + * - if `tvar` is nonvariant in `vmap`, include all variables in its upper and lower + * bounds as non-variant. + * Do this in a fixpoint iteration until `vmap` stabilizes. + */ + def propagate(vmap: VarianceMap): VarianceMap = { + var vmap1 = vmap + def traverse(tp: Type) = { vmap1 = accu(vmap1, tp) } + vmap.foreachBinding { (tvar, v) => + val param = tvar.origin + val e = constraint.entry(param) + accu.setVariance(v) + if (v >= 0) { + traverse(e.bounds.lo) + constraint.lower(param).foreach(p => traverse(constraint.typeVarOfParam(p))) + } + if (v <= 0) { + traverse(e.bounds.hi) + constraint.upper(param).foreach(p => traverse(constraint.typeVarOfParam(p))) + } + } + if (vmap1 eq vmap) vmap else propagate(vmap1) + } + + propagate(accu(SimpleMap.Empty, tp)) + } +} + +/** An enumeration controlling the degree of forcing in "is-dully-defined" checks. */ +@sharable object ForceDegree { + class Value(val appliesTo: TypeVar => Boolean, val minimizeAll: Boolean) + val none = new Value(_ => false, minimizeAll = false) + val all = new Value(_ => true, minimizeAll = false) + val noBottom = new Value(_ => true, minimizeAll = false) +} + diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala new file mode 100644 index 000000000..3931fcaf4 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -0,0 +1,539 @@ +package dotty.tools +package dotc +package typer + +import dotty.tools.dotc.ast.Trees.NamedArg +import dotty.tools.dotc.ast.{Trees, untpd, tpd, TreeTypeMap} +import Trees._ +import core._ +import Flags._ +import Symbols._ +import Types._ +import Decorators._ +import Constants._ +import StdNames.nme +import Contexts.Context +import Names.{Name, TermName} +import NameOps._ +import SymDenotations.SymDenotation +import Annotations._ +import transform.ExplicitOuter +import Inferencing.fullyDefinedType +import config.Printers.inlining +import ErrorReporting.errorTree +import collection.mutable +import transform.TypeUtils._ + +object Inliner { + import tpd._ + + /** Adds accessors accessors for all non-public term members accessed + * from `tree`. Non-public type members are currently left as they are. + * This means that references to a private type will lead to typing failures + * on the code when it is inlined. Less than ideal, but hard to do better (see below). + * + * @return If there are accessors generated, a thicket consisting of the rewritten `tree` + * and all accessors, otherwise the original tree. + */ + private def makeInlineable(tree: Tree)(implicit ctx: Context) = { + + /** A tree map which inserts accessors for all non-public term members accessed + * from inlined code. Accesors are collected in the `accessors` buffer. + */ + object addAccessors extends TreeMap { + val inlineMethod = ctx.owner + val accessors = new mutable.ListBuffer[MemberDef] + + /** A definition needs an accessor if it is private, protected, or qualified private */ + def needsAccessor(sym: Symbol)(implicit ctx: Context) = + sym.is(AccessFlags) || sym.privateWithin.exists + + /** The name of the next accessor to be generated */ + def accessorName(implicit ctx: Context) = + ctx.freshNames.newName(inlineMethod.name.asTermName.inlineAccessorName.toString) + + /** A fresh accessor symbol. + * + * @param tree The tree representing the original access to the non-public member + * @param accessorInfo The type of the accessor + */ + def accessorSymbol(tree: Tree, accessorInfo: Type)(implicit ctx: Context): Symbol = + ctx.newSymbol( + owner = inlineMethod.owner, + name = if (tree.isTerm) accessorName.toTermName else accessorName.toTypeName, + flags = if (tree.isTerm) Synthetic | Method else Synthetic, + info = accessorInfo, + coord = tree.pos).entered + + /** Add an accessor to a non-public method and replace the original access with a + * call to the accessor. + * + * @param tree The original access to the non-public symbol + * @param refPart The part that refers to the method or field of the original access + * @param targs All type arguments passed in the access, if any + * @param argss All value arguments passed in the access, if any + * @param accessedType The type of the accessed method or field, as seen from the access site. + * @param rhs A function that builds the right-hand side of the accessor, + * given a reference to the accessed symbol and any type and + * value arguments the need to be integrated. + * @return The call to the accessor method that replaces the original access. + */ + def addAccessor(tree: Tree, refPart: Tree, targs: List[Tree], argss: List[List[Tree]], + accessedType: Type, rhs: (Tree, List[Type], List[List[Tree]]) => Tree)(implicit ctx: Context): Tree = { + val qual = qualifier(refPart) + def refIsLocal = qual match { + case qual: This => qual.symbol == refPart.symbol.owner + case _ => false + } + val (accessorDef, accessorRef) = + if (refPart.symbol.isStatic || refIsLocal) { + // Easy case: Reference to a static symbol or a symbol referenced via `this.` + val accessorType = accessedType.ensureMethodic + val accessor = accessorSymbol(tree, accessorType).asTerm + val accessorDef = polyDefDef(accessor, tps => argss => + rhs(refPart, tps, argss)) + val accessorRef = ref(accessor).appliedToTypeTrees(targs).appliedToArgss(argss) + (accessorDef, accessorRef) + } else { + // Hard case: Reference needs to go via a dynamic prefix + inlining.println(i"adding inline accessor for $tree -> (${qual.tpe}, $refPart: ${refPart.getClass}, [$targs%, %], ($argss%, %))") + + // Need to dealias in order to catch all possible references to abstracted over types in + // substitutions + val dealiasMap = new TypeMap { + def apply(t: Type) = mapOver(t.dealias) + } + + val qualType = dealiasMap(qual.tpe.widen) + + // Add qualifier type as leading method argument to argument `tp` + def addQualType(tp: Type): Type = tp match { + case tp: PolyType => tp.derivedPolyType(tp.paramNames, tp.paramBounds, addQualType(tp.resultType)) + case tp: ExprType => addQualType(tp.resultType) + case tp => MethodType(qualType :: Nil, tp) + } + + // The types that are local to the inlined method, and that therefore have + // to be abstracted out in the accessor, which is external to the inlined method + val localRefs = qualType.namedPartsWith(_.symbol.isContainedIn(inlineMethod)).toList + + // Abstract accessed type over local refs + def abstractQualType(mtpe: Type): Type = + if (localRefs.isEmpty) mtpe + else mtpe.LambdaAbstract(localRefs.map(_.symbol)).asInstanceOf[PolyType].flatten + + val accessorType = abstractQualType(addQualType(dealiasMap(accessedType))) + val accessor = accessorSymbol(tree, accessorType).asTerm + + val accessorDef = polyDefDef(accessor, tps => argss => + rhs(argss.head.head.select(refPart.symbol), tps.drop(localRefs.length), argss.tail)) + + val accessorRef = ref(accessor) + .appliedToTypeTrees(localRefs.map(TypeTree(_)) ++ targs) + .appliedToArgss((qual :: Nil) :: argss) + (accessorDef, accessorRef) + } + accessors += accessorDef + inlining.println(i"added inline accessor: $accessorDef") + accessorRef + } + + override def transform(tree: Tree)(implicit ctx: Context): Tree = super.transform { + tree match { + case _: Apply | _: TypeApply | _: RefTree if needsAccessor(tree.symbol) => + if (tree.isTerm) { + val (methPart, targs, argss) = decomposeCall(tree) + addAccessor(tree, methPart, targs, argss, + accessedType = methPart.tpe.widen, + rhs = (qual, tps, argss) => qual.appliedToTypes(tps).appliedToArgss(argss)) + } else { + // TODO: Handle references to non-public types. + // This is quite tricky, as such types can appear anywhere, including as parts + // of types of other things. For the moment we do nothing and complain + // at the implicit expansion site if there's a reference to an inaccessible type. + // Draft code (incomplete): + // + // val accessor = accessorSymbol(tree, TypeAlias(tree.tpe)).asType + // myAccessors += TypeDef(accessor) + // ref(accessor) + // + tree + } + case Assign(lhs: RefTree, rhs) if needsAccessor(lhs.symbol) => + addAccessor(tree, lhs, Nil, (rhs :: Nil) :: Nil, + accessedType = MethodType(rhs.tpe.widen :: Nil, defn.UnitType), + rhs = (lhs, tps, argss) => lhs.becomes(argss.head.head)) + case _ => tree + } + } + } + + val tree1 = addAccessors.transform(tree) + flatTree(tree1 :: addAccessors.accessors.toList) + } + + /** Register inline info for given inline method `sym`. + * + * @param sym The symbol denotatioon of the inline method for which info is registered + * @param treeExpr A function that computes the tree to be inlined, given a context + * This tree may still refer to non-public members. + * @param ctx The context to use for evaluating `treeExpr`. It needs + * to have the inlined method as owner. + */ + def registerInlineInfo( + sym: SymDenotation, treeExpr: Context => Tree)(implicit ctx: Context): Unit = { + sym.unforcedAnnotation(defn.BodyAnnot) match { + case Some(ann: ConcreteBodyAnnotation) => + case Some(ann: LazyBodyAnnotation) if ann.isEvaluated => + case _ => + if (!ctx.isAfterTyper) { + val inlineCtx = ctx + sym.updateAnnotation(LazyBodyAnnotation { _ => + implicit val ctx: Context = inlineCtx + ctx.withNoError(treeExpr(ctx))(makeInlineable) + }) + } + } + } + + /** `sym` has an inline method with a known body to inline (note: definitions coming + * from Scala2x class files might be `@inline`, but still lack that body. + */ + def hasBodyToInline(sym: SymDenotation)(implicit ctx: Context): Boolean = + sym.isInlineMethod && sym.hasAnnotation(defn.BodyAnnot) + + private def bodyAndAccessors(sym: SymDenotation)(implicit ctx: Context): (Tree, List[MemberDef]) = + sym.unforcedAnnotation(defn.BodyAnnot).get.tree match { + case Thicket(body :: accessors) => (body, accessors.asInstanceOf[List[MemberDef]]) + case body => (body, Nil) + } + + /** The body to inline for method `sym`. + * @pre hasBodyToInline(sym) + */ + def bodyToInline(sym: SymDenotation)(implicit ctx: Context): Tree = + bodyAndAccessors(sym)._1 + + /** The accessors to non-public members needed by the inlinable body of `sym`. + * These accessors are dropped as a side effect of calling this method. + * @pre hasBodyToInline(sym) + */ + def removeInlineAccessors(sym: SymDenotation)(implicit ctx: Context): List[MemberDef] = { + val (body, accessors) = bodyAndAccessors(sym) + if (accessors.nonEmpty) sym.updateAnnotation(ConcreteBodyAnnotation(body)) + accessors + } + + /** Try to inline a call to a `@inline` method. Fail with error if the maximal + * inline depth is exceeded. + * + * @param tree The call to inline + * @param pt The expected type of the call. + * @return An `Inlined` node that refers to the original call and the inlined bindings + * and body that replace it. + */ + def inlineCall(tree: Tree, pt: Type)(implicit ctx: Context): Tree = + if (enclosingInlineds.length < ctx.settings.xmaxInlines.value) + new Inliner(tree, bodyToInline(tree.symbol)).inlined(pt) + else errorTree( + tree, + i"""|Maximal number of successive inlines (${ctx.settings.xmaxInlines.value}) exceeded, + |Maybe this is caused by a recursive inline method? + |You can use -Xmax:inlines to change the limit.""" + ) + + /** Replace `Inlined` node by a block that contains its bindings and expansion */ + def dropInlined(inlined: tpd.Inlined)(implicit ctx: Context): Tree = { + val reposition = new TreeMap { + override def transform(tree: Tree)(implicit ctx: Context): Tree = { + super.transform(tree).withPos(inlined.call.pos) + } + } + tpd.seq(inlined.bindings, reposition.transform(inlined.expansion)) + } + + /** The qualifier part of a Select or Ident. + * For an Ident, this is the `This` of the current class. (TODO: use elsewhere as well?) + */ + private def qualifier(tree: Tree)(implicit ctx: Context) = tree match { + case Select(qual, _) => qual + case _ => This(ctx.owner.enclosingClass.asClass) + } +} + +/** Produces an inlined version of `call` via its `inlined` method. + * + * @param call The original call to a `@inline` method + * @param rhs The body of the inline method that replaces the call. + */ +class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { + import tpd._ + import Inliner._ + + private val (methPart, targs, argss) = decomposeCall(call) + private val meth = methPart.symbol + private val prefix = qualifier(methPart) + + // Make sure all type arguments to the call are fully determined + for (targ <- targs) fullyDefinedType(targ.tpe, "inlined type argument", targ.pos) + + /** A map from parameter names of the inline method to references of the actual arguments. + * For a type argument this is the full argument type. + * For a value argument, it is a reference to either the argument value + * (if the argument is a pure expression of singleton type), or to `val` or `def` acting + * as a proxy (if the argument is something else). + */ + private val paramBinding = new mutable.HashMap[Name, Type] + + /** A map from references to (type and value) parameters of the inline method + * to their corresponding argument or proxy references, as given by `paramBinding`. + */ + private val paramProxy = new mutable.HashMap[Type, Type] + + /** A map from the classes of (direct and outer) this references in `rhs` + * to references of their proxies. + * Note that we can't index by the ThisType itself since there are several + * possible forms to express what is logicaly the same ThisType. E.g. + * + * ThisType(TypeRef(ThisType(p), cls)) + * + * vs + * + * ThisType(TypeRef(TermRef(ThisType(<root>), p), cls)) + * + * These are different (wrt ==) types but represent logically the same key + */ + private val thisProxy = new mutable.HashMap[ClassSymbol, TermRef] + + /** A buffer for bindings that define proxies for actual arguments */ + val bindingsBuf = new mutable.ListBuffer[ValOrDefDef] + + computeParamBindings(meth.info, targs, argss) + + private def newSym(name: Name, flags: FlagSet, info: Type): Symbol = + ctx.newSymbol(ctx.owner, name, flags, info, coord = call.pos) + + /** Populate `paramBinding` and `bindingsBuf` by matching parameters with + * corresponding arguments. `bindingbuf` will be further extended later by + * proxies to this-references. + */ + private def computeParamBindings(tp: Type, targs: List[Tree], argss: List[List[Tree]]): Unit = tp match { + case tp: PolyType => + (tp.paramNames, targs).zipped.foreach { (name, arg) => + paramBinding(name) = arg.tpe.stripTypeVar + } + computeParamBindings(tp.resultType, Nil, argss) + case tp: MethodType => + (tp.paramNames, tp.paramTypes, argss.head).zipped.foreach { (name, paramtp, arg) => + def isByName = paramtp.dealias.isInstanceOf[ExprType] + paramBinding(name) = arg.tpe.stripAnnots.stripTypeVar match { + case argtpe: SingletonType if isByName || isIdempotentExpr(arg) => argtpe + case argtpe => + val inlineFlag = if (paramtp.hasAnnotation(defn.InlineParamAnnot)) Inline else EmptyFlags + val (bindingFlags, bindingType) = + if (isByName) (inlineFlag | Method, ExprType(argtpe.widen)) + else (inlineFlag, argtpe.widen) + val boundSym = newSym(name, bindingFlags, bindingType).asTerm + val binding = + if (isByName) DefDef(boundSym, arg.changeOwner(ctx.owner, boundSym)) + else ValDef(boundSym, arg) + bindingsBuf += binding + boundSym.termRef + } + } + computeParamBindings(tp.resultType, targs, argss.tail) + case _ => + assert(targs.isEmpty) + assert(argss.isEmpty) + } + + /** Populate `thisProxy` and `paramProxy` as follows: + * + * 1a. If given type refers to a static this, thisProxy binds it to corresponding global reference, + * 1b. If given type refers to an instance this, create a proxy symbol and bind the thistype to + * refer to the proxy. The proxy is not yet entered in `bindingsBuf` that will come later. + * 2. If given type refers to a parameter, make `paramProxy` refer to the entry stored + * in `paramNames` under the parameter's name. This roundabout way to bind parameter + * references to proxies is done because we not known a priori what the parameter + * references of a method are (we only know the method's type, but that contains PolyParams + * and MethodParams, not TypeRefs or TermRefs. + */ + private def registerType(tpe: Type): Unit = tpe match { + case tpe: ThisType + if !ctx.owner.isContainedIn(tpe.cls) && !tpe.cls.is(Package) && + !thisProxy.contains(tpe.cls) => + if (tpe.cls.isStaticOwner) + thisProxy(tpe.cls) = tpe.cls.sourceModule.termRef + else { + val proxyName = s"${tpe.cls.name}_this".toTermName + val proxyType = tpe.asSeenFrom(prefix.tpe, meth.owner) + thisProxy(tpe.cls) = newSym(proxyName, EmptyFlags, proxyType).termRef + registerType(meth.owner.thisType) // make sure we have a base from which to outer-select + } + case tpe: NamedType + if tpe.symbol.is(Param) && tpe.symbol.owner == meth && + !paramProxy.contains(tpe) => + paramProxy(tpe) = paramBinding(tpe.name) + case _ => + } + + /** Register type of leaf node */ + private def registerLeaf(tree: Tree): Unit = tree match { + case _: This | _: Ident | _: TypeTree => + tree.tpe.foreachPart(registerType, stopAtStatic = true) + case _ => + } + + /** The Inlined node representing the inlined call */ + def inlined(pt: Type) = { + // make sure prefix is executed if it is impure + if (!isIdempotentExpr(prefix)) registerType(meth.owner.thisType) + + // Register types of all leaves of inlined body so that the `paramProxy` and `thisProxy` maps are defined. + rhs.foreachSubTree(registerLeaf) + + // The class that the this-proxy `selfSym` represents + def classOf(selfSym: Symbol) = selfSym.info.widen.classSymbol + + // The name of the outer selector that computes the rhs of `selfSym` + def outerSelector(selfSym: Symbol): TermName = classOf(selfSym).name.toTermName ++ nme.OUTER_SELECT + + // The total nesting depth of the class represented by `selfSym`. + def outerLevel(selfSym: Symbol): Int = classOf(selfSym).ownersIterator.length + + // All needed this-proxies, sorted by nesting depth of the classes they represent (innermost first) + val accessedSelfSyms = thisProxy.values.toList.map(_.symbol).sortBy(-outerLevel(_)) + + // Compute val-definitions for all this-proxies and append them to `bindingsBuf` + var lastSelf: Symbol = NoSymbol + for (selfSym <- accessedSelfSyms) { + val rhs = + if (!lastSelf.exists) + prefix + else + untpd.Select(ref(lastSelf), outerSelector(selfSym)).withType(selfSym.info) + bindingsBuf += ValDef(selfSym.asTerm, rhs) + lastSelf = selfSym + } + + // The type map to apply to the inlined tree. This maps references to this-types + // and parameters to type references of their arguments or proxies. + val typeMap = new TypeMap { + def apply(t: Type) = t match { + case t: ThisType => thisProxy.getOrElse(t.cls, t) + case t: TypeRef => paramProxy.getOrElse(t, mapOver(t)) + case t: SingletonType => paramProxy.getOrElse(t, mapOver(t)) + case t => mapOver(t) + } + } + + // The tree map to apply to the inlined tree. This maps references to this-types + // and parameters to references of their arguments or their proxies. + def treeMap(tree: Tree) = { + tree match { + case _: This => + tree.tpe match { + case thistpe: ThisType => + thisProxy.get(thistpe.cls) match { + case Some(t) => ref(t).withPos(tree.pos) + case None => tree + } + case _ => tree + } + case _: Ident => + paramProxy.get(tree.tpe) match { + case Some(t: SingletonType) if tree.isTerm => singleton(t).withPos(tree.pos) + case Some(t) if tree.isType => TypeTree(t).withPos(tree.pos) + case None => tree + } + case _ => tree + }} + + // The complete translation maps referenves to this and parameters to + // corresponding arguments or proxies on the type and term level. It also changes + // the owner from the inlined method to the current owner. + val inliner = new TreeTypeMap(typeMap, treeMap, meth :: Nil, ctx.owner :: Nil) + + val expansion = inliner(rhs.withPos(call.pos)) + ctx.traceIndented(i"inlining $call\n, BINDINGS =\n${bindingsBuf.toList}%\n%\nEXPANSION =\n$expansion", inlining, show = true) { + + // The final expansion runs a typing pass over the inlined tree. See InlineTyper for details. + val expansion1 = InlineTyper.typed(expansion, pt)(inlineContext(call)) + + /** Does given definition bind a closure that will be inlined? */ + def bindsDeadClosure(defn: ValOrDefDef) = Ident(defn.symbol.termRef) match { + case InlineableClosure(_) => !InlineTyper.retainedClosures.contains(defn.symbol) + case _ => false + } + + /** All bindings in `bindingsBuf` except bindings of inlineable closures */ + val bindings = bindingsBuf.toList.filterNot(bindsDeadClosure).map(_.withPos(call.pos)) + + tpd.Inlined(call, bindings, expansion1) + } + } + + /** An extractor for references to closure arguments that refer to `@inline` methods */ + private object InlineableClosure { + lazy val paramProxies = paramProxy.values.toSet + def unapply(tree: Ident)(implicit ctx: Context): Option[Tree] = + if (paramProxies.contains(tree.tpe)) { + bindingsBuf.find(_.name == tree.name) match { + case Some(ddef: ValDef) if ddef.symbol.is(Inline) => + ddef.rhs match { + case closure(_, meth, _) => Some(meth) + case _ => None + } + case _ => None + } + } else None + } + + /** A typer for inlined code. Its purpose is: + * 1. Implement constant folding over inlined code + * 2. Selectively expand ifs with constant conditions + * 3. Inline arguments that are inlineable closures + * 4. Make sure inlined code is type-correct. + * 5. Make sure that the tree's typing is idempotent (so that future -Ycheck passes succeed) + */ + private object InlineTyper extends ReTyper { + + var retainedClosures = Set[Symbol]() + + override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context) = { + val tree1 = super.typedIdent(tree, pt) + tree1 match { + case InlineableClosure(_) => retainedClosures += tree.symbol + case _ => + } + tree1 + } + + override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { + val res = super.typedSelect(tree, pt) + ensureAccessible(res.tpe, tree.qualifier.isInstanceOf[untpd.Super], tree.pos) + res + } + + override def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context) = { + val cond1 = typed(tree.cond, defn.BooleanType) + cond1.tpe.widenTermRefExpr match { + case ConstantType(Constant(condVal: Boolean)) => + val selected = typed(if (condVal) tree.thenp else tree.elsep, pt) + if (isIdempotentExpr(cond1)) selected + else Block(cond1 :: Nil, selected) + case _ => + val if1 = untpd.cpy.If(tree)(cond = untpd.TypedSplice(cond1)) + super.typedIf(if1, pt) + } + } + + override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context) = tree.asInstanceOf[tpd.Tree] match { + case Apply(Select(InlineableClosure(fn), nme.apply), args) => + inlining.println(i"reducing $tree with closure $fn") + typed(fn.appliedToArgs(args), pt) + case _ => + super.typedApply(tree, pt) + } + } +} diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala new file mode 100644 index 000000000..148cf1da7 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -0,0 +1,1061 @@ +package dotty.tools +package dotc +package typer + +import core._ +import ast._ +import Trees._, Constants._, StdNames._, Scopes._, Denotations._, Comments._ +import Contexts._, Symbols._, Types._, SymDenotations._, Names._, NameOps._, Flags._, Decorators._ +import ast.desugar, ast.desugar._ +import ProtoTypes._ +import util.Positions._ +import util.{Property, SourcePosition, DotClass} +import collection.mutable +import annotation.tailrec +import ErrorReporting._ +import tpd.ListOfTreeDecorator +import config.Config +import config.Printers.{typr, completions, noPrinter} +import Annotations._ +import Inferencing._ +import transform.ValueClasses._ +import TypeApplications._ +import language.implicitConversions +import reporting.diagnostic.messages._ + +trait NamerContextOps { this: Context => + + /** Enter symbol into current class, if current class is owner of current context, + * or into current scope, if not. Should always be called instead of scope.enter + * in order to make sure that updates to class members are reflected in + * finger prints. + */ + def enter(sym: Symbol): Symbol = { + ctx.owner match { + case cls: ClassSymbol => cls.enter(sym) + case _ => this.scope.openForMutations.enter(sym) + } + sym + } + + /** The denotation with the given name in current context */ + def denotNamed(name: Name): Denotation = + if (owner.isClass) + if (outer.owner == owner) { // inner class scope; check whether we are referring to self + if (scope.size == 1) { + val elem = scope.lastEntry + if (elem.name == name) return elem.sym.denot // return self + } + assert(scope.size <= 1, scope) + owner.thisType.member(name) + } + else // we are in the outermost context belonging to a class; self is invisible here. See inClassContext. + owner.findMember(name, owner.thisType, EmptyFlags) + else + scope.denotsNamed(name).toDenot(NoPrefix) + + /** Either the current scope, or, if the current context owner is a class, + * the declarations of the current class. + */ + def effectiveScope: Scope = + if (owner != null && owner.isClass) owner.asClass.unforcedDecls + else scope + + /** The symbol (stored in some typer's symTree) of an enclosing context definition */ + def symOfContextTree(tree: untpd.Tree) = { + def go(ctx: Context): Symbol = { + ctx.typeAssigner match { + case typer: Typer => + tree.getAttachment(typer.SymOfTree) match { + case Some(sym) => sym + case None => + var cx = ctx.outer + while (cx.typeAssigner eq typer) cx = cx.outer + go(cx) + } + case _ => NoSymbol + } + } + go(this) + } + + /** Context where `sym` is defined, assuming we are in a nested context. */ + def defContext(sym: Symbol) = + outersIterator + .dropWhile(_.owner != sym) + .dropWhile(_.owner == sym) + .next + + /** The given type, unless `sym` is a constructor, in which case the + * type of the constructed instance is returned + */ + def effectiveResultType(sym: Symbol, typeParams: List[Symbol], given: Type) = + if (sym.name == nme.CONSTRUCTOR) sym.owner.typeRef.appliedTo(typeParams map (_.typeRef)) + else given + + /** if isConstructor, make sure it has one non-implicit parameter list */ + def normalizeIfConstructor(paramSymss: List[List[Symbol]], isConstructor: Boolean) = + if (isConstructor && + (paramSymss.isEmpty || paramSymss.head.nonEmpty && (paramSymss.head.head is Implicit))) + Nil :: paramSymss + else + paramSymss + + /** The method type corresponding to given parameters and result type */ + def methodType(typeParams: List[Symbol], valueParamss: List[List[Symbol]], resultType: Type, isJava: Boolean = false)(implicit ctx: Context): Type = { + val monotpe = + (valueParamss :\ resultType) { (params, resultType) => + val make = + if (params.nonEmpty && (params.head is Implicit)) ImplicitMethodType + else if (isJava) JavaMethodType + else MethodType + if (isJava) + for (param <- params) + if (param.info.isDirectRef(defn.ObjectClass)) param.info = defn.AnyType + make.fromSymbols(params, resultType) + } + if (typeParams.nonEmpty) monotpe.LambdaAbstract(typeParams) + else if (valueParamss.isEmpty) ExprType(monotpe) + else monotpe + } + + /** Find moduleClass/sourceModule in effective scope */ + private def findModuleBuddy(name: Name)(implicit ctx: Context) = { + val scope = effectiveScope + val it = scope.lookupAll(name).filter(_ is Module) + assert(it.hasNext, s"no companion $name in $scope") + it.next + } + + /** Add moduleClass or sourceModule functionality to completer + * for a module or module class + */ + def adjustModuleCompleter(completer: LazyType, name: Name) = + if (name.isTermName) + completer withModuleClass (_ => findModuleBuddy(name.moduleClassName)) + else + completer withSourceModule (_ => findModuleBuddy(name.sourceModuleName)) +} + +/** This class creates symbols from definitions and imports and gives them + * lazy types. + * + * Timeline: + * + * During enter, trees are expanded as necessary, populating the expandedTree map. + * Symbols are created, and the symOfTree map is set up. + * + * Symbol completion causes some trees to be already typechecked and typedTree + * entries are created to associate the typed trees with the untyped expanded originals. + * + * During typer, original trees are first expanded using expandedTree. For each + * expanded member definition or import we extract and remove the corresponding symbol + * from the symOfTree map and complete it. We then consult the typedTree map to see + * whether a typed tree exists already. If yes, the typed tree is returned as result. + * Otherwise, we proceed with regular type checking. + * + * The scheme is designed to allow sharing of nodes, as long as each duplicate appears + * in a different method. + */ +class Namer { typer: Typer => + + import untpd._ + + val TypedAhead = new Property.Key[tpd.Tree] + val ExpandedTree = new Property.Key[Tree] + val SymOfTree = new Property.Key[Symbol] + + /** A partial map from unexpanded member and pattern defs and to their expansions. + * Populated during enterSyms, emptied during typer. + */ + //lazy val expandedTree = new mutable.AnyRefMap[DefTree, Tree] + /*{ + override def default(tree: DefTree) = tree // can't have defaults on AnyRefMaps :-( + }*/ + + /** A map from expanded MemberDef, PatDef or Import trees to their symbols. + * Populated during enterSyms, emptied at the point a typed tree + * with the same symbol is created (this can be when the symbol is completed + * or at the latest when the tree is typechecked. + */ + //lazy val symOfTree = new mutable.AnyRefMap[Tree, Symbol] + + /** A map from expanded trees to their typed versions. + * Populated when trees are typechecked during completion (using method typedAhead). + */ + // lazy val typedTree = new mutable.AnyRefMap[Tree, tpd.Tree] + + /** A map from method symbols to nested typers. + * Populated when methods are completed. Emptied when they are typechecked. + * The nested typer contains new versions of the four maps above including this + * one, so that trees that are shared between different DefDefs can be independently + * used as indices. It also contains a scope that contains nested parameters. + */ + lazy val nestedTyper = new mutable.AnyRefMap[Symbol, Typer] + + /** The scope of the typer. + * For nested typers this is a place parameters are entered during completion + * and where they survive until typechecking. A context with this typer also + * has this scope. + */ + val scope = newScope + + /** The symbol of the given expanded tree. */ + def symbolOfTree(tree: Tree)(implicit ctx: Context): Symbol = { + val xtree = expanded(tree) + xtree.getAttachment(TypedAhead) match { + case Some(ttree) => ttree.symbol + case none => xtree.attachment(SymOfTree) + } + } + + /** The enclosing class with given name; error if none exists */ + def enclosingClassNamed(name: TypeName, pos: Position)(implicit ctx: Context): Symbol = { + if (name.isEmpty) NoSymbol + else { + val cls = ctx.owner.enclosingClassNamed(name) + if (!cls.exists) ctx.error(s"no enclosing class or object is named $name", pos) + cls + } + } + + /** Record `sym` as the symbol defined by `tree` */ + def recordSym(sym: Symbol, tree: Tree)(implicit ctx: Context): Symbol = { + val refs = tree.attachmentOrElse(References, Nil) + if (refs.nonEmpty) { + tree.removeAttachment(References) + refs foreach (_.pushAttachment(OriginalSymbol, sym)) + } + tree.pushAttachment(SymOfTree, sym) + sym + } + + /** If this tree is a member def or an import, create a symbol of it + * and store in symOfTree map. + */ + def createSymbol(tree: Tree)(implicit ctx: Context): Symbol = { + + def privateWithinClass(mods: Modifiers) = + enclosingClassNamed(mods.privateWithin, mods.pos) + + def checkFlags(flags: FlagSet) = + if (flags.isEmpty) flags + else { + val (ok, adapted, kind) = tree match { + case tree: TypeDef => (flags.isTypeFlags, flags.toTypeFlags, "type") + case _ => (flags.isTermFlags, flags.toTermFlags, "value") + } + if (!ok) + ctx.error(i"modifier(s) `$flags' incompatible with $kind definition", tree.pos) + adapted + } + + /** Add moduleClass/sourceModule to completer if it is for a module val or class */ + def adjustIfModule(completer: LazyType, tree: MemberDef) = + if (tree.mods is Module) ctx.adjustModuleCompleter(completer, tree.name.encode) + else completer + + typr.println(i"creating symbol for $tree in ${ctx.mode}") + + def checkNoConflict(name: Name): Name = { + def errorName(msg: => String) = { + ctx.error(msg, tree.pos) + name.freshened + } + def preExisting = ctx.effectiveScope.lookup(name) + if (ctx.owner is PackageClass) + if (preExisting.isDefinedInCurrentRun) + errorName(s"${preExisting.showLocated} has already been compiled\nonce during this run") + else name + else + if ((!ctx.owner.isClass || name.isTypeName) && preExisting.exists) + errorName(i"$name is already defined as $preExisting") + else name + } + + val inSuperCall = if (ctx.mode is Mode.InSuperCall) InSuperCall else EmptyFlags + + tree match { + case tree: TypeDef if tree.isClassDef => + val name = checkNoConflict(tree.name.encode).asTypeName + val flags = checkFlags(tree.mods.flags &~ Implicit) + val cls = recordSym(ctx.newClassSymbol( + ctx.owner, name, flags | inSuperCall, + cls => adjustIfModule(new ClassCompleter(cls, tree)(ctx), tree), + privateWithinClass(tree.mods), tree.namePos, ctx.source.file), tree) + cls.completer.asInstanceOf[ClassCompleter].init() + cls + case tree: MemberDef => + val name = checkNoConflict(tree.name.encode) + val flags = checkFlags(tree.mods.flags) + val isDeferred = lacksDefinition(tree) + val deferred = if (isDeferred) Deferred else EmptyFlags + val method = if (tree.isInstanceOf[DefDef]) Method else EmptyFlags + val inSuperCall1 = if (tree.mods is ParamOrAccessor) EmptyFlags else inSuperCall + // suppress inSuperCall for constructor parameters + val higherKinded = tree match { + case TypeDef(_, PolyTypeTree(_, _)) if isDeferred => HigherKinded + case _ => EmptyFlags + } + + // to complete a constructor, move one context further out -- this + // is the context enclosing the class. Note that the context in which a + // constructor is recorded and the context in which it is completed are + // different: The former must have the class as owner (because the + // constructor is owned by the class), the latter must not (because + // constructor parameters are interpreted as if they are outside the class). + // Don't do this for Java constructors because they need to see the import + // of the companion object, and it is not necessary for them because they + // have no implementation. + val cctx = if (tree.name == nme.CONSTRUCTOR && !(tree.mods is JavaDefined)) ctx.outer else ctx + + val completer = tree match { + case tree: TypeDef => new TypeDefCompleter(tree)(cctx) + case _ => new Completer(tree)(cctx) + } + + recordSym(ctx.newSymbol( + ctx.owner, name, flags | deferred | method | higherKinded | inSuperCall1, + adjustIfModule(completer, tree), + privateWithinClass(tree.mods), tree.namePos), tree) + case tree: Import => + recordSym(ctx.newSymbol( + ctx.owner, nme.IMPORT, Synthetic, new Completer(tree), NoSymbol, tree.pos), tree) + case _ => + NoSymbol + } + } + + /** If `sym` exists, enter it in effective scope. Check that + * package members are not entered twice in the same run. + */ + def enterSymbol(sym: Symbol)(implicit ctx: Context) = { + if (sym.exists) { + typr.println(s"entered: $sym in ${ctx.owner} and ${ctx.effectiveScope}") + ctx.enter(sym) + } + sym + } + + /** Create package if it does not yet exist. */ + private def createPackageSymbol(pid: RefTree)(implicit ctx: Context): Symbol = { + val pkgOwner = pid match { + case Ident(_) => if (ctx.owner eq defn.EmptyPackageClass) defn.RootClass else ctx.owner + case Select(qual: RefTree, _) => createPackageSymbol(qual).moduleClass + } + val existing = pkgOwner.info.decls.lookup(pid.name) + + if ((existing is Package) && (pkgOwner eq existing.owner)) existing + else { + /** If there's already an existing type, then the package is a dup of this type */ + val existingType = pkgOwner.info.decls.lookup(pid.name.toTypeName) + if (existingType.exists) { + ctx.error(PkgDuplicateSymbol(existingType), pid.pos) + ctx.newCompletePackageSymbol(pkgOwner, (pid.name ++ "$_error_").toTermName).entered + } + else ctx.newCompletePackageSymbol(pkgOwner, pid.name.asTermName).entered + } + } + + /** Expand tree and store in `expandedTree` */ + def expand(tree: Tree)(implicit ctx: Context): Unit = tree match { + case mdef: DefTree => + val expanded = desugar.defTree(mdef) + typr.println(i"Expansion: $mdef expands to $expanded") + if (expanded ne mdef) mdef.pushAttachment(ExpandedTree, expanded) + case _ => + } + + /** The expanded version of this tree, or tree itself if not expanded */ + def expanded(tree: Tree)(implicit ctx: Context): Tree = tree match { + case ddef: DefTree => ddef.attachmentOrElse(ExpandedTree, ddef) + case _ => tree + } + + /** A new context that summarizes an import statement */ + def importContext(sym: Symbol, selectors: List[Tree])(implicit ctx: Context) = + ctx.fresh.setImportInfo(new ImportInfo(sym, selectors)) + + /** A new context for the interior of a class */ + def inClassContext(selfInfo: DotClass /* Should be Type | Symbol*/)(implicit ctx: Context): Context = { + val localCtx: Context = ctx.fresh.setNewScope + selfInfo match { + case sym: Symbol if sym.exists && sym.name != nme.WILDCARD => + localCtx.scope.openForMutations.enter(sym) + case _ => + } + localCtx + } + + /** For all class definitions `stat` in `xstats`: If the companion class if + * not also defined in `xstats`, invalidate it by setting its info to + * NoType. + */ + def invalidateCompanions(pkg: Symbol, xstats: List[untpd.Tree])(implicit ctx: Context): Unit = { + val definedNames = xstats collect { case stat: NameTree => stat.name } + def invalidate(name: TypeName) = + if (!(definedNames contains name)) { + val member = pkg.info.decl(name).asSymDenotation + if (member.isClass && !(member is Package)) member.info = NoType + } + xstats foreach { + case stat: TypeDef if stat.isClassDef => + invalidate(stat.name.moduleClassName) + case _ => + } + } + + /** Expand tree and create top-level symbols for statement and enter them into symbol table */ + def index(stat: Tree)(implicit ctx: Context): Context = { + expand(stat) + indexExpanded(stat) + } + + /** Create top-level symbols for all statements in the expansion of this statement and + * enter them into symbol table + */ + def indexExpanded(origStat: Tree)(implicit ctx: Context): Context = { + def recur(stat: Tree): Context = stat match { + case pcl: PackageDef => + val pkg = createPackageSymbol(pcl.pid) + index(pcl.stats)(ctx.fresh.setOwner(pkg.moduleClass)) + invalidateCompanions(pkg, Trees.flatten(pcl.stats map expanded)) + setDocstring(pkg, stat) + ctx + case imp: Import => + importContext(createSymbol(imp), imp.selectors) + case mdef: DefTree => + val sym = enterSymbol(createSymbol(mdef)) + setDocstring(sym, origStat) + addEnumConstants(mdef, sym) + ctx + case stats: Thicket => + stats.toList.foreach(recur) + ctx + case _ => + ctx + } + recur(expanded(origStat)) + } + + /** Determines whether this field holds an enum constant. + * To qualify, the following conditions must be met: + * - The field's class has the ENUM flag set + * - The field's class extends java.lang.Enum + * - The field has the ENUM flag set + * - The field is static + * - The field is stable + */ + def isEnumConstant(vd: ValDef)(implicit ctx: Context) = { + // val ownerHasEnumFlag = + // Necessary to check because scalac puts Java's static members into the companion object + // while Scala's enum constants live directly in the class. + // We don't check for clazz.superClass == JavaEnumClass, because this causes a illegal + // cyclic reference error. See the commit message for details. + // if (ctx.compilationUnit.isJava) ctx.owner.companionClass.is(Enum) else ctx.owner.is(Enum) + vd.mods.is(allOf(Enum, Stable, JavaStatic, JavaDefined)) // && ownerHasEnumFlag + } + + /** Add java enum constants */ + def addEnumConstants(mdef: DefTree, sym: Symbol)(implicit ctx: Context): Unit = mdef match { + case vdef: ValDef if (isEnumConstant(vdef)) => + val enumClass = sym.owner.linkedClass + if (!(enumClass is Flags.Sealed)) enumClass.setFlag(Flags.AbstractSealed) + enumClass.addAnnotation(Annotation.makeChild(sym)) + case _ => + } + + + def setDocstring(sym: Symbol, tree: Tree)(implicit ctx: Context) = tree match { + case t: MemberDef if t.rawComment.isDefined => + ctx.docCtx.foreach(_.addDocstring(sym, t.rawComment)) + case _ => () + } + + /** Create top-level symbols for statements and enter them into symbol table */ + def index(stats: List[Tree])(implicit ctx: Context): Context = { + + val classDef = mutable.Map[TypeName, TypeDef]() + val moduleDef = mutable.Map[TypeName, TypeDef]() + + /** Merge the definitions of a synthetic companion generated by a case class + * and the real companion, if both exist. + */ + def mergeCompanionDefs() = { + for (cdef @ TypeDef(name, _) <- stats) + if (cdef.isClassDef) { + classDef(name) = cdef + cdef.attachmentOrElse(ExpandedTree, cdef) match { + case Thicket(cls :: mval :: (mcls @ TypeDef(_, _: Template)) :: crest) => + moduleDef(name) = mcls + case _ => + } + } + for (mdef @ ModuleDef(name, _) <- stats if !mdef.mods.is(Flags.Package)) { + val typName = name.toTypeName + val Thicket(vdef :: (mcls @ TypeDef(_, impl: Template)) :: Nil) = mdef.attachment(ExpandedTree) + moduleDef(typName) = mcls + classDef get name.toTypeName match { + case Some(cdef) => + cdef.attachmentOrElse(ExpandedTree, cdef) match { + case Thicket(cls :: mval :: TypeDef(_, compimpl: Template) :: crest) => + val mcls1 = cpy.TypeDef(mcls)( + rhs = cpy.Template(impl)(body = compimpl.body ++ impl.body)) + mdef.putAttachment(ExpandedTree, Thicket(vdef :: mcls1 :: Nil)) + moduleDef(typName) = mcls1 + cdef.putAttachment(ExpandedTree, Thicket(cls :: crest)) + case _ => + } + case none => + } + } + } + + def createLinks(classTree: TypeDef, moduleTree: TypeDef)(implicit ctx: Context) = { + val claz = ctx.denotNamed(classTree.name.encode).symbol + val modl = ctx.denotNamed(moduleTree.name.encode).symbol + ctx.synthesizeCompanionMethod(nme.COMPANION_CLASS_METHOD, claz, modl).entered + ctx.synthesizeCompanionMethod(nme.COMPANION_MODULE_METHOD, modl, claz).entered + } + + def createCompanionLinks(implicit ctx: Context): Unit = { + for (cdef @ TypeDef(name, _) <- classDef.values) { + moduleDef.getOrElse(name, EmptyTree) match { + case t: TypeDef => + createLinks(cdef, t) + case EmptyTree => + } + } + } + + stats foreach expand + mergeCompanionDefs() + val ctxWithStats = (ctx /: stats) ((ctx, stat) => indexExpanded(stat)(ctx)) + createCompanionLinks(ctxWithStats) + ctxWithStats + } + + /** The completer of a symbol defined by a member def or import (except ClassSymbols) */ + class Completer(val original: Tree)(implicit ctx: Context) extends LazyType { + + protected def localContext(owner: Symbol) = ctx.fresh.setOwner(owner).setTree(original) + + protected def typeSig(sym: Symbol): Type = original match { + case original: ValDef => + if (sym is Module) moduleValSig(sym) + else valOrDefDefSig(original, sym, Nil, Nil, identity)(localContext(sym).setNewScope) + case original: DefDef => + val typer1 = ctx.typer.newLikeThis + nestedTyper(sym) = typer1 + typer1.defDefSig(original, sym)(localContext(sym).setTyper(typer1)) + case imp: Import => + try { + val expr1 = typedAheadExpr(imp.expr, AnySelectionProto) + ImportType(expr1) + } catch { + case ex: CyclicReference => + typr.println(s"error while completing ${imp.expr}") + throw ex + } + } + + final override def complete(denot: SymDenotation)(implicit ctx: Context) = { + if (completions != noPrinter && ctx.typerState != this.ctx.typerState) { + completions.println(completions.getClass.toString) + def levels(c: Context): Int = + if (c.typerState eq this.ctx.typerState) 0 + else if (c.typerState == null) -1 + else if (c.outer.typerState == c.typerState) levels(c.outer) + else levels(c.outer) + 1 + completions.println(s"!!!completing ${denot.symbol.showLocated} in buried typerState, gap = ${levels(ctx)}") + } + completeInCreationContext(denot) + } + + protected def addAnnotations(denot: SymDenotation): Unit = original match { + case original: untpd.MemberDef => + var hasInlineAnnot = false + for (annotTree <- untpd.modsDeco(original).mods.annotations) { + val cls = typedAheadAnnotation(annotTree) + val ann = Annotation.deferred(cls, implicit ctx => typedAnnotation(annotTree)) + denot.addAnnotation(ann) + if (cls == defn.InlineAnnot && denot.is(Method, butNot = Accessor)) + denot.setFlag(Inline) + } + case _ => + } + + private def addInlineInfo(denot: SymDenotation) = original match { + case original: untpd.DefDef if denot.isInlineMethod => + Inliner.registerInlineInfo( + denot, + implicit ctx => typedAheadExpr(original).asInstanceOf[tpd.DefDef].rhs + )(localContext(denot.symbol)) + case _ => + } + + /** Intentionally left without `implicit ctx` parameter. We need + * to pick up the context at the point where the completer was created. + */ + def completeInCreationContext(denot: SymDenotation): Unit = { + addAnnotations(denot) + addInlineInfo(denot) + denot.info = typeSig(denot.symbol) + Checking.checkWellFormed(denot.symbol) + } + } + + class TypeDefCompleter(original: TypeDef)(ictx: Context) extends Completer(original)(ictx) with TypeParamsCompleter { + private var myTypeParams: List[TypeSymbol] = null + private var nestedCtx: Context = null + assert(!original.isClassDef) + + def completerTypeParams(sym: Symbol)(implicit ctx: Context): List[TypeSymbol] = { + if (myTypeParams == null) { + //println(i"completing type params of $sym in ${sym.owner}") + nestedCtx = localContext(sym).setNewScope + myTypeParams = { + implicit val ctx: Context = nestedCtx + val tparams = original.rhs match { + case PolyTypeTree(tparams, _) => tparams + case _ => Nil + } + completeParams(tparams) + tparams.map(symbolOfTree(_).asType) + } + } + myTypeParams + } + + override protected def typeSig(sym: Symbol): Type = + typeDefSig(original, sym, completerTypeParams(sym)(ictx))(nestedCtx) + } + + class ClassCompleter(cls: ClassSymbol, original: TypeDef)(ictx: Context) extends Completer(original)(ictx) { + withDecls(newScope) + + protected implicit val ctx: Context = localContext(cls).setMode(ictx.mode &~ Mode.InSuperCall) + + val TypeDef(name, impl @ Template(constr, parents, self, _)) = original + + val (params, rest) = impl.body span { + case td: TypeDef => td.mods is Param + case vd: ValDef => vd.mods is ParamAccessor + case _ => false + } + + def init() = index(params) + + /** The type signature of a ClassDef with given symbol */ + override def completeInCreationContext(denot: SymDenotation): Unit = { + + /* The type of a parent constructor. Types constructor arguments + * only if parent type contains uninstantiated type parameters. + */ + def parentType(parent: untpd.Tree)(implicit ctx: Context): Type = + if (parent.isType) { + typedAheadType(parent, AnyTypeConstructorProto).tpe + } else { + val (core, targs) = stripApply(parent) match { + case TypeApply(core, targs) => (core, targs) + case core => (core, Nil) + } + val Select(New(tpt), nme.CONSTRUCTOR) = core + val targs1 = targs map (typedAheadType(_)) + val ptype = typedAheadType(tpt).tpe appliedTo targs1.tpes + if (ptype.typeParams.isEmpty) ptype + else typedAheadExpr(parent).tpe + } + + /* Check parent type tree `parent` for the following well-formedness conditions: + * (1) It must be a class type with a stable prefix (@see checkClassTypeWithStablePrefix) + * (2) If may not derive from itself + * (3) Overriding type parameters must be correctly forwarded. (@see checkTypeParamOverride) + * (4) The class is not final + * (5) If the class is sealed, it is defined in the same compilation unit as the current class + */ + def checkedParentType(parent: untpd.Tree, paramAccessors: List[Symbol]): Type = { + val ptype = parentType(parent)(ctx.superCallContext) + if (cls.isRefinementClass) ptype + else { + val pt = checkClassType(ptype, parent.pos, + traitReq = parent ne parents.head, stablePrefixReq = true) + if (pt.derivesFrom(cls)) { + val addendum = parent match { + case Select(qual: Super, _) if ctx.scala2Mode => + "\n(Note that inheriting a class of the same name is no longer allowed)" + case _ => "" + } + ctx.error(i"cyclic inheritance: $cls extends itself$addendum", parent.pos) + defn.ObjectType + } + else if (!paramAccessors.forall(checkTypeParamOverride(pt, _))) + defn.ObjectType + else { + val pclazz = pt.typeSymbol + if (pclazz.is(Final)) + ctx.error(em"cannot extend final $pclazz", cls.pos) + if (pclazz.is(Sealed) && pclazz.associatedFile != cls.associatedFile) + ctx.error(em"cannot extend sealed $pclazz in different compilation unit", cls.pos) + pt + } + } + } + + /* Check that every parameter with the same name as a visible named parameter in the parent + * class satisfies the following two conditions: + * (1) The overriding parameter is also named (i.e. not local/name mangled). + * (2) The overriding parameter is passed on directly to the parent parameter, or the + * parent parameter is not fully defined. + * @return true if conditions are satisfied, false otherwise. + */ + def checkTypeParamOverride(parent: Type, paramAccessor: Symbol): Boolean = { + var ok = true + val pname = paramAccessor.name + + def illegal(how: String): Unit = { + ctx.error(em"Illegal override of public type parameter $pname in $parent$how", paramAccessor.pos) + ok = false + } + + def checkAlias(tp: Type): Unit = tp match { + case tp: RefinedType => + if (tp.refinedName == pname) + tp.refinedInfo match { + case TypeAlias(alias) => + alias match { + case TypeRef(pre, name1) if name1 == pname && (pre =:= cls.thisType) => + // OK, parameter is passed on directly + case _ => + illegal(em".\nParameter is both redeclared and instantiated with $alias.") + } + case _ => // OK, argument is not fully defined + } + else checkAlias(tp.parent) + case _ => + } + if (parent.nonPrivateMember(paramAccessor.name).symbol.is(Param)) + if (paramAccessor is Private) + illegal("\nwith private parameter. Parameter definition needs to be prefixed with `type'.") + else + checkAlias(parent) + ok + } + + addAnnotations(denot) + + val selfInfo = + if (self.isEmpty) NoType + else if (cls.is(Module)) { + val moduleType = cls.owner.thisType select sourceModule + if (self.name == nme.WILDCARD) moduleType + else recordSym( + ctx.newSymbol(cls, self.name, self.mods.flags, moduleType, coord = self.pos), + self) + } + else createSymbol(self) + + // pre-set info, so that parent types can refer to type params + val tempInfo = new TempClassInfo(cls.owner.thisType, cls, decls, selfInfo) + denot.info = tempInfo + + // Ensure constructor is completed so that any parameter accessors + // which have type trees deriving from its parameters can be + // completed in turn. Note that parent types access such parameter + // accessors, that's why the constructor needs to be completed before + // the parent types are elaborated. + index(constr) + symbolOfTree(constr).ensureCompleted() + + index(rest)(inClassContext(selfInfo)) + + val tparamAccessors = decls.filter(_ is TypeParamAccessor).toList + val parentTypes = ensureFirstIsClass(parents.map(checkedParentType(_, tparamAccessors))) + val parentRefs = ctx.normalizeToClassRefs(parentTypes, cls, decls) + typr.println(s"completing $denot, parents = $parents, parentTypes = $parentTypes, parentRefs = $parentRefs") + + tempInfo.finalize(denot, parentRefs) + + Checking.checkWellFormed(cls) + if (isDerivedValueClass(cls)) cls.setFlag(Final) + cls.setApplicableFlags( + (NoInitsInterface /: impl.body)((fs, stat) => fs & defKind(stat))) + } + } + + /** Typecheck tree during completion, and remember result in typedtree map */ + private def typedAheadImpl(tree: Tree, pt: Type)(implicit ctx: Context): tpd.Tree = { + val xtree = expanded(tree) + xtree.getAttachment(TypedAhead) match { + case Some(ttree) => ttree + case none => + val ttree = typer.typed(tree, pt) + xtree.pushAttachment(TypedAhead, ttree) + ttree + } + } + + def typedAheadType(tree: Tree, pt: Type = WildcardType)(implicit ctx: Context): tpd.Tree = + typedAheadImpl(tree, pt)(ctx retractMode Mode.PatternOrType addMode Mode.Type) + + def typedAheadExpr(tree: Tree, pt: Type = WildcardType)(implicit ctx: Context): tpd.Tree = + typedAheadImpl(tree, pt)(ctx retractMode Mode.PatternOrType) + + def typedAheadAnnotation(tree: Tree)(implicit ctx: Context): Symbol = tree match { + case Apply(fn, _) => typedAheadAnnotation(fn) + case TypeApply(fn, _) => typedAheadAnnotation(fn) + case Select(qual, nme.CONSTRUCTOR) => typedAheadAnnotation(qual) + case New(tpt) => typedAheadType(tpt).tpe.classSymbol + } + + /** Enter and typecheck parameter list */ + def completeParams(params: List[MemberDef])(implicit ctx: Context) = { + index(params) + for (param <- params) typedAheadExpr(param) + } + + /** The signature of a module valdef. + * This will compute the corresponding module class TypeRef immediately + * without going through the defined type of the ValDef. This is necessary + * to avoid cyclic references involving imports and module val defs. + */ + def moduleValSig(sym: Symbol)(implicit ctx: Context): Type = { + val clsName = sym.name.moduleClassName + val cls = ctx.denotNamed(clsName) suchThat (_ is ModuleClass) + ctx.owner.thisType select (clsName, cls) + } + + /** The type signature of a ValDef or DefDef + * @param mdef The definition + * @param sym Its symbol + * @param paramFn A wrapping function that produces the type of the + * defined symbol, given its final return type + */ + def valOrDefDefSig(mdef: ValOrDefDef, sym: Symbol, typeParams: List[Symbol], paramss: List[List[Symbol]], paramFn: Type => Type)(implicit ctx: Context): Type = { + + def inferredType = { + /** A type for this definition that might be inherited from elsewhere: + * If this is a setter parameter, the corresponding getter type. + * If this is a class member, the conjunction of all result types + * of overridden methods. + * NoType if neither case holds. + */ + val inherited = + if (sym.owner.isTerm) NoType + else { + // TODO: Look only at member of supertype instead? + lazy val schema = paramFn(WildcardType) + val site = sym.owner.thisType + ((NoType: Type) /: sym.owner.info.baseClasses.tail) { (tp, cls) => + def instantiatedResType(info: Type, tparams: List[Symbol], paramss: List[List[Symbol]]): Type = info match { + case info: PolyType => + if (info.paramNames.length == typeParams.length) + instantiatedResType(info.instantiate(tparams.map(_.typeRef)), Nil, paramss) + else NoType + case info: MethodType => + paramss match { + case params :: paramss1 if info.paramNames.length == params.length => + instantiatedResType(info.instantiate(params.map(_.termRef)), tparams, paramss1) + case _ => + NoType + } + case _ => + if (tparams.isEmpty && paramss.isEmpty) info.widenExpr + else NoType + } + val iRawInfo = + cls.info.nonPrivateDecl(sym.name).matchingDenotation(site, schema).info + val iResType = instantiatedResType(iRawInfo, typeParams, paramss).asSeenFrom(site, cls) + if (iResType.exists) + typr.println(i"using inherited type for ${mdef.name}; raw: $iRawInfo, inherited: $iResType") + tp & iResType + } + } + + /** The proto-type to be used when inferring the result type from + * the right hand side. This is `WildcardType` except if the definition + * is a default getter. In that case, the proto-type is the type of + * the corresponding parameter where bound parameters are replaced by + * Wildcards. + */ + def rhsProto = { + val name = sym.asTerm.name + val idx = name.defaultGetterIndex + if (idx < 0) WildcardType + else { + val original = name.defaultGetterToMethod + val meth: Denotation = + if (original.isConstructorName && (sym.owner is ModuleClass)) + sym.owner.companionClass.info.decl(nme.CONSTRUCTOR) + else + ctx.defContext(sym).denotNamed(original) + def paramProto(paramss: List[List[Type]], idx: Int): Type = paramss match { + case params :: paramss1 => + if (idx < params.length) wildApprox(params(idx)) + else paramProto(paramss1, idx - params.length) + case nil => + WildcardType + } + val defaultAlts = meth.altsWith(_.hasDefaultParams) + if (defaultAlts.length == 1) + paramProto(defaultAlts.head.info.widen.paramTypess, idx) + else + WildcardType + } + } + + // println(s"final inherited for $sym: ${inherited.toString}") !!! + // println(s"owner = ${sym.owner}, decls = ${sym.owner.info.decls.show}") + def isInline = sym.is(FinalOrInline, butNot = Method | Mutable) + + // Widen rhs type and approximate `|' but keep ConstantTypes if + // definition is inline (i.e. final in Scala2). + def widenRhs(tp: Type): Type = tp.widenTermRefExpr match { + case tp: ConstantType if isInline => tp + case _ => ctx.harmonizeUnion(tp.widen) + } + + // Replace aliases to Unit by Unit itself. If we leave the alias in + // it would be erased to BoxedUnit. + def dealiasIfUnit(tp: Type) = if (tp.isRef(defn.UnitClass)) defn.UnitType else tp + + val rhsCtx = ctx.addMode(Mode.InferringReturnType) + def rhsType = typedAheadExpr(mdef.rhs, inherited orElse rhsProto)(rhsCtx).tpe + def cookedRhsType = ctx.deskolemize(dealiasIfUnit(widenRhs(rhsType))) + lazy val lhsType = fullyDefinedType(cookedRhsType, "right-hand side", mdef.pos) + //if (sym.name.toString == "y") println(i"rhs = $rhsType, cooked = $cookedRhsType") + if (inherited.exists) + if (sym.is(Final, butNot = Method) && lhsType.isInstanceOf[ConstantType]) + lhsType // keep constant types that fill in for a non-constant (to be revised when inline has landed). + else inherited + else { + if (sym is Implicit) { + val resStr = if (mdef.isInstanceOf[DefDef]) "result " else "" + ctx.error(s"${resStr}type of implicit definition needs to be given explicitly", mdef.pos) + sym.resetFlag(Implicit) + } + lhsType orElse WildcardType + } + } + + val tptProto = mdef.tpt match { + case _: untpd.DerivedTypeTree => + WildcardType + case TypeTree() => + inferredType + case TypedSplice(tpt: TypeTree) if !isFullyDefined(tpt.tpe, ForceDegree.none) => + val rhsType = typedAheadExpr(mdef.rhs, tpt.tpe).tpe + mdef match { + case mdef: DefDef if mdef.name == nme.ANON_FUN => + val hygienicType = avoid(rhsType, paramss.flatten) + if (!(hygienicType <:< tpt.tpe)) + ctx.error(i"return type ${tpt.tpe} of lambda cannot be made hygienic;\n" + + i"it is not a supertype of the hygienic type $hygienicType", mdef.pos) + //println(i"lifting $rhsType over $paramss -> $hygienicType = ${tpt.tpe}") + //println(TypeComparer.explained { implicit ctx => hygienicType <:< tpt.tpe }) + case _ => + } + WildcardType + case _ => + WildcardType + } + paramFn(typedAheadType(mdef.tpt, tptProto).tpe) + } + + /** The type signature of a DefDef with given symbol */ + def defDefSig(ddef: DefDef, sym: Symbol)(implicit ctx: Context) = { + val DefDef(name, tparams, vparamss, _, _) = ddef + val isConstructor = name == nme.CONSTRUCTOR + + // The following 3 lines replace what was previously just completeParams(tparams). + // But that can cause bad bounds being computed, as witnessed by + // tests/pos/paramcycle.scala. The problematic sequence is this: + // 0. Class constructor gets completed. + // 1. Type parameter CP of constructor gets completed + // 2. As a first step CP's bounds are set to Nothing..Any. + // 3. CP's real type bound demands the completion of corresponding type parameter DP + // of enclosing class. + // 4. Type parameter DP has a rhs a DerivedFromParam tree, as installed by + // desugar.classDef + // 5. The completion of DP then copies the current bounds of CP, which are still Nothing..Any. + // 6. The completion of CP finishes installing the real type bounds. + // Consequence: CP ends up with the wrong bounds! + // To avoid this we always complete type parameters of a class before the type parameters + // of the class constructor, but after having indexed the constructor parameters (because + // indexing is needed to provide a symbol to copy for DP's completion. + // With the patch, we get instead the following sequence: + // 0. Class constructor gets completed. + // 1. Class constructor parameter CP is indexed. + // 2. Class parameter DP starts completion. + // 3. Info of CP is computed (to be copied to DP). + // 4. CP is completed. + // 5. Info of CP is copied to DP and DP is completed. + index(tparams) + if (isConstructor) sym.owner.typeParams.foreach(_.ensureCompleted()) + for (tparam <- tparams) typedAheadExpr(tparam) + + vparamss foreach completeParams + def typeParams = tparams map symbolOfTree + val paramSymss = ctx.normalizeIfConstructor(vparamss.nestedMap(symbolOfTree), isConstructor) + def wrapMethType(restpe: Type): Type = { + val restpe1 = // try to make anonymous functions non-dependent, so that they can be used in closures + if (name == nme.ANON_FUN) avoid(restpe, paramSymss.flatten) + else restpe + ctx.methodType(tparams map symbolOfTree, paramSymss, restpe1, isJava = ddef.mods is JavaDefined) + } + if (isConstructor) { + // set result type tree to unit, but take the current class as result type of the symbol + typedAheadType(ddef.tpt, defn.UnitType) + wrapMethType(ctx.effectiveResultType(sym, typeParams, NoType)) + } + else valOrDefDefSig(ddef, sym, typeParams, paramSymss, wrapMethType) + } + + def typeDefSig(tdef: TypeDef, sym: Symbol, tparamSyms: List[TypeSymbol])(implicit ctx: Context): Type = { + def abstracted(tp: Type): Type = + if (tparamSyms.nonEmpty) tp.LambdaAbstract(tparamSyms) else tp + + val dummyInfo = abstracted(TypeBounds.empty) + sym.info = dummyInfo + // Temporarily set info of defined type T to ` >: Nothing <: Any. + // This is done to avoid cyclic reference errors for F-bounds. + // This is subtle: `sym` has now an empty TypeBounds, but is not automatically + // made an abstract type. If it had been made an abstract type, it would count as an + // abstract type of its enclosing class, which might make that class an invalid + // prefix. I verified this would lead to an error when compiling io.ClassPath. + // A distilled version is in pos/prefix.scala. + // + // The scheme critically relies on an implementation detail of isRef, which + // inspects a TypeRef's info, instead of simply dealiasing alias types. + + val isDerived = tdef.rhs.isInstanceOf[untpd.DerivedTypeTree] + val rhs = tdef.rhs match { + case PolyTypeTree(_, body) => body + case rhs => rhs + } + val rhsBodyType = typedAheadType(rhs).tpe + val rhsType = if (isDerived) rhsBodyType else abstracted(rhsBodyType) + val unsafeInfo = rhsType match { + case bounds: TypeBounds => bounds + case alias => TypeAlias(alias, if (sym is Local) sym.variance else 0) + } + if (isDerived) sym.info = unsafeInfo + else { + sym.info = NoCompleter + sym.info = checkNonCyclic(sym, unsafeInfo, reportErrors = true) + } + + // Here we pay the price for the cavalier setting info to TypeBounds.empty above. + // We need to compensate by invalidating caches in references that might + // still contain the TypeBounds.empty. If we do not do this, stdlib factories + // fail with a bounds error in PostTyper. + def ensureUpToDate(tp: Type, outdated: Type) = tp match { + case tref: TypeRef if tref.info == outdated && sym.info != outdated => + tref.uncheckedSetSym(null) + case _ => + } + ensureUpToDate(sym.typeRef, dummyInfo) + ensureUpToDate(sym.typeRef.appliedTo(tparamSyms.map(_.typeRef)), TypeBounds.empty) + sym.info + } +} diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala new file mode 100644 index 000000000..9a20a452e --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -0,0 +1,488 @@ +package dotty.tools +package dotc +package typer + +import core._ +import ast._ +import Contexts._, Types._, Flags._, Denotations._, Names._, StdNames._, NameOps._, Symbols._ +import Trees._ +import Constants._ +import Scopes._ +import annotation.unchecked +import util.Positions._ +import util.{Stats, SimpleMap} +import util.common._ +import Decorators._ +import Uniques._ +import ErrorReporting.errorType +import config.Printers.typr +import collection.mutable + +object ProtoTypes { + + import tpd._ + + /** A trait defining an `isCompatible` method. */ + trait Compatibility { + + /** Is there an implicit conversion from `tp` to `pt`? */ + def viewExists(tp: Type, pt: Type)(implicit ctx: Context): Boolean + + /** A type `tp` is compatible with a type `pt` if one of the following holds: + * 1. `tp` is a subtype of `pt` + * 2. `pt` is by name parameter type, and `tp` is compatible with its underlying type + * 3. there is an implicit conversion from `tp` to `pt`. + * 4. `tp` is a numeric subtype of `pt` (this case applies even if implicit conversions are disabled) + */ + def isCompatible(tp: Type, pt: Type)(implicit ctx: Context): Boolean = + (tp.widenExpr relaxed_<:< pt.widenExpr) || viewExists(tp, pt) + + /** Test compatibility after normalization in a fresh typerstate. */ + def normalizedCompatible(tp: Type, pt: Type)(implicit ctx: Context) = { + val nestedCtx = ctx.fresh.setExploreTyperState + isCompatible(normalize(tp, pt)(nestedCtx), pt)(nestedCtx) + } + + private def disregardProto(pt: Type)(implicit ctx: Context): Boolean = pt.dealias match { + case _: OrType => true + case pt => pt.isRef(defn.UnitClass) + } + + /** Check that the result type of the current method + * fits the given expected result type. + */ + def constrainResult(mt: Type, pt: Type)(implicit ctx: Context): Boolean = pt match { + case pt: FunProto => + mt match { + case mt: MethodType => + mt.isDependent || constrainResult(mt.resultType, pt.resultType) + case _ => + true + } + case _: ValueTypeOrProto if !disregardProto(pt) => + mt match { + case mt: MethodType => + mt.isDependent || isCompatible(normalize(mt, pt), pt) + case _ => + isCompatible(mt, pt) + } + case _: WildcardType => + isCompatible(mt, pt) + case _ => + true + } + } + + object NoViewsAllowed extends Compatibility { + override def viewExists(tp: Type, pt: Type)(implicit ctx: Context): Boolean = false + } + + /** A trait for prototypes that match all types */ + trait MatchAlways extends ProtoType { + def isMatchedBy(tp1: Type)(implicit ctx: Context) = true + def map(tm: TypeMap)(implicit ctx: Context): ProtoType = this + def fold[T](x: T, ta: TypeAccumulator[T])(implicit ctx: Context): T = x + } + + /** A class marking ignored prototypes that can be revealed by `deepenProto` */ + case class IgnoredProto(ignored: Type) extends UncachedGroundType with MatchAlways { + override def deepenProto(implicit ctx: Context): Type = ignored + } + + /** A prototype for expressions [] that are part of a selection operation: + * + * [ ].name: proto + */ + abstract case class SelectionProto(val name: Name, val memberProto: Type, val compat: Compatibility) + extends CachedProxyType with ProtoType with ValueTypeOrProto { + + override def isMatchedBy(tp1: Type)(implicit ctx: Context) = { + name == nme.WILDCARD || { + val mbr = tp1.member(name) + def qualifies(m: SingleDenotation) = + memberProto.isRef(defn.UnitClass) || + compat.normalizedCompatible(m.info, memberProto) + mbr match { // hasAltWith inlined for performance + case mbr: SingleDenotation => mbr.exists && qualifies(mbr) + case _ => mbr hasAltWith qualifies + } + } + } + + def underlying(implicit ctx: Context) = WildcardType + + def derivedSelectionProto(name: Name, memberProto: Type, compat: Compatibility)(implicit ctx: Context) = + if ((name eq this.name) && (memberProto eq this.memberProto) && (compat eq this.compat)) this + else SelectionProto(name, memberProto, compat) + + override def equals(that: Any): Boolean = that match { + case that: SelectionProto => + (name eq that.name) && (memberProto == that.memberProto) && (compat eq that.compat) + case _ => + false + } + + def map(tm: TypeMap)(implicit ctx: Context) = derivedSelectionProto(name, tm(memberProto), compat) + def fold[T](x: T, ta: TypeAccumulator[T])(implicit ctx: Context) = ta(x, memberProto) + + override def deepenProto(implicit ctx: Context) = derivedSelectionProto(name, memberProto.deepenProto, compat) + + override def computeHash = addDelta(doHash(name, memberProto), if (compat eq NoViewsAllowed) 1 else 0) + } + + class CachedSelectionProto(name: Name, memberProto: Type, compat: Compatibility) extends SelectionProto(name, memberProto, compat) + + object SelectionProto { + def apply(name: Name, memberProto: Type, compat: Compatibility)(implicit ctx: Context): SelectionProto = { + val selproto = new CachedSelectionProto(name, memberProto, compat) + if (compat eq NoViewsAllowed) unique(selproto) else selproto + } + } + + /** Create a selection proto-type, but only one level deep; + * treat constructors specially + */ + def selectionProto(name: Name, tp: Type, typer: Typer)(implicit ctx: Context) = + if (name.isConstructorName) WildcardType + else tp match { + case tp: UnapplyFunProto => new UnapplySelectionProto(name) + case tp => SelectionProto(name, IgnoredProto(tp), typer) + } + + /** A prototype for expressions [] that are in some unspecified selection operation + * + * [].?: ? + * + * Used to indicate that expression is in a context where the only valid + * operation is further selection. In this case, the expression need not be a value. + * @see checkValue + */ + @sharable object AnySelectionProto extends SelectionProto(nme.WILDCARD, WildcardType, NoViewsAllowed) + + /** A prototype for selections in pattern constructors */ + class UnapplySelectionProto(name: Name) extends SelectionProto(name, WildcardType, NoViewsAllowed) + + trait ApplyingProto extends ProtoType + + /** A prototype for expressions that appear in function position + * + * [](args): resultType + */ + case class FunProto(args: List[untpd.Tree], resType: Type, typer: Typer)(implicit ctx: Context) + extends UncachedGroundType with ApplyingProto { + private var myTypedArgs: List[Tree] = Nil + + override def resultType(implicit ctx: Context) = resType + + /** A map in which typed arguments can be stored to be later integrated in `typedArgs`. */ + private var myTypedArg: SimpleMap[untpd.Tree, Tree] = SimpleMap.Empty + + /** A map recording the typer states in which arguments stored in myTypedArg were typed */ + private var evalState: SimpleMap[untpd.Tree, TyperState] = SimpleMap.Empty + + def isMatchedBy(tp: Type)(implicit ctx: Context) = + typer.isApplicable(tp, Nil, typedArgs, resultType) + + def derivedFunProto(args: List[untpd.Tree] = this.args, resultType: Type, typer: Typer = this.typer) = + if ((args eq this.args) && (resultType eq this.resultType) && (typer eq this.typer)) this + else new FunProto(args, resultType, typer) + + override def notApplied = WildcardType + + /** Forget the types of any arguments that have been typed producing a constraint in a + * typer state that is not yet committed into the one of the current context `ctx`. + * This is necessary to avoid "orphan" PolyParams that are referred to from + * type variables in the typed arguments, but that are not registered in the + * current constraint. A test case is pos/t1756.scala. + * @return True if all arguments have types (in particular, no types were forgotten). + */ + def allArgTypesAreCurrent()(implicit ctx: Context): Boolean = { + evalState foreachBinding { (arg, tstate) => + if (tstate.uncommittedAncestor.constraint ne ctx.typerState.constraint) { + typr.println(i"need to invalidate $arg / ${myTypedArg(arg)}, ${tstate.constraint}, current = ${ctx.typerState.constraint}") + myTypedArg = myTypedArg.remove(arg) + evalState = evalState.remove(arg) + } + } + myTypedArg.size == args.length + } + + private def cacheTypedArg(arg: untpd.Tree, typerFn: untpd.Tree => Tree)(implicit ctx: Context): Tree = { + var targ = myTypedArg(arg) + if (targ == null) { + targ = typerFn(arg) + if (!ctx.reporter.hasPending) { + myTypedArg = myTypedArg.updated(arg, targ) + evalState = evalState.updated(arg, ctx.typerState) + } + } + targ + } + + /** The typed arguments. This takes any arguments already typed using + * `typedArg` into account. + */ + def typedArgs: List[Tree] = { + if (myTypedArgs.size != args.length) + myTypedArgs = args.mapconserve(cacheTypedArg(_, typer.typed(_))) + myTypedArgs + } + + /** Type single argument and remember the unadapted result in `myTypedArg`. + * used to avoid repeated typings of trees when backtracking. + */ + def typedArg(arg: untpd.Tree, formal: Type)(implicit ctx: Context): Tree = { + val targ = cacheTypedArg(arg, typer.typedUnadapted(_, formal)) + typer.adapt(targ, formal, arg) + } + + private var myTupled: Type = NoType + + /** The same proto-type but with all arguments combined in a single tuple */ + def tupled: FunProto = myTupled match { + case pt: FunProto => + pt + case _ => + myTupled = new FunProto(untpd.Tuple(args) :: Nil, resultType, typer) + tupled + } + + /** Somebody called the `tupled` method of this prototype */ + def isTupled: Boolean = myTupled.isInstanceOf[FunProto] + + override def toString = s"FunProto(${args mkString ","} => $resultType)" + + def map(tm: TypeMap)(implicit ctx: Context): FunProto = + derivedFunProto(args, tm(resultType), typer) + + def fold[T](x: T, ta: TypeAccumulator[T])(implicit ctx: Context): T = + ta(ta.foldOver(x, typedArgs.tpes), resultType) + + override def deepenProto(implicit ctx: Context) = derivedFunProto(args, resultType.deepenProto, typer) + } + + + /** A prototype for expressions that appear in function position + * + * [](args): resultType, where args are known to be typed + */ + class FunProtoTyped(args: List[tpd.Tree], resultType: Type, typer: Typer)(implicit ctx: Context) extends FunProto(args, resultType, typer)(ctx) { + override def typedArgs = args + } + + /** A prototype for implicitly inferred views: + * + * []: argType => resultType + */ + abstract case class ViewProto(argType: Type, resType: Type) + extends CachedGroundType with ApplyingProto { + + override def resultType(implicit ctx: Context) = resType + + def isMatchedBy(tp: Type)(implicit ctx: Context): Boolean = + ctx.typer.isApplicable(tp, argType :: Nil, resultType) + + def derivedViewProto(argType: Type, resultType: Type)(implicit ctx: Context) = + if ((argType eq this.argType) && (resultType eq this.resultType)) this + else ViewProto(argType, resultType) + + def map(tm: TypeMap)(implicit ctx: Context): ViewProto = derivedViewProto(tm(argType), tm(resultType)) + + def fold[T](x: T, ta: TypeAccumulator[T])(implicit ctx: Context): T = + ta(ta(x, argType), resultType) + + override def deepenProto(implicit ctx: Context) = derivedViewProto(argType, resultType.deepenProto) + } + + class CachedViewProto(argType: Type, resultType: Type) extends ViewProto(argType, resultType) { + override def computeHash = doHash(argType, resultType) + } + + object ViewProto { + def apply(argType: Type, resultType: Type)(implicit ctx: Context) = + unique(new CachedViewProto(argType, resultType)) + } + + class UnapplyFunProto(argType: Type, typer: Typer)(implicit ctx: Context) extends FunProto( + untpd.TypedSplice(dummyTreeOfType(argType))(ctx) :: Nil, WildcardType, typer) + + /** A prototype for expressions [] that are type-parameterized: + * + * [] [targs] resultType + */ + case class PolyProto(targs: List[Type], resType: Type) extends UncachedGroundType with ProtoType { + + override def resultType(implicit ctx: Context) = resType + + override def isMatchedBy(tp: Type)(implicit ctx: Context) = { + def isInstantiatable(tp: Type) = tp.widen match { + case tp: PolyType => tp.paramNames.length == targs.length + case _ => false + } + isInstantiatable(tp) || tp.member(nme.apply).hasAltWith(d => isInstantiatable(d.info)) + } + + def derivedPolyProto(targs: List[Type], resultType: Type) = + if ((targs eq this.targs) && (resType eq this.resType)) this + else PolyProto(targs, resType) + + override def notApplied = WildcardType + + def map(tm: TypeMap)(implicit ctx: Context): PolyProto = + derivedPolyProto(targs mapConserve tm, tm(resultType)) + + def fold[T](x: T, ta: TypeAccumulator[T])(implicit ctx: Context): T = + ta(ta.foldOver(x, targs), resultType) + + override def deepenProto(implicit ctx: Context) = derivedPolyProto(targs, resultType.deepenProto) + } + + /** A prototype for expressions [] that are known to be functions: + * + * [] _ + */ + @sharable object AnyFunctionProto extends UncachedGroundType with MatchAlways + + /** A prototype for type constructors that are followed by a type application */ + @sharable object AnyTypeConstructorProto extends UncachedGroundType with MatchAlways + + /** Add all parameters in given polytype `pt` to the constraint's domain. + * If the constraint contains already some of these parameters in its domain, + * make a copy of the polytype and add the copy's type parameters instead. + * Return either the original polytype, or the copy, if one was made. + * Also, if `owningTree` is non-empty, add a type variable for each parameter. + * @return The added polytype, and the list of created type variables. + */ + def constrained(pt: PolyType, owningTree: untpd.Tree)(implicit ctx: Context): (PolyType, List[TypeVar]) = { + val state = ctx.typerState + assert(!(ctx.typerState.isCommittable && owningTree.isEmpty), + s"inconsistent: no typevars were added to committable constraint ${state.constraint}") + + def newTypeVars(pt: PolyType): List[TypeVar] = + for (n <- (0 until pt.paramNames.length).toList) + yield new TypeVar(PolyParam(pt, n), state, owningTree, ctx.owner) + + val added = + if (state.constraint contains pt) pt.newLikeThis(pt.paramNames, pt.paramBounds, pt.resultType) + else pt + val tvars = if (owningTree.isEmpty) Nil else newTypeVars(added) + ctx.typeComparer.addToConstraint(added, tvars) + (added, tvars) + } + + /** Same as `constrained(pt, EmptyTree)`, but returns just the created polytype */ + def constrained(pt: PolyType)(implicit ctx: Context): PolyType = constrained(pt, EmptyTree)._1 + + /** The normalized form of a type + * - unwraps polymorphic types, tracking their parameters in the current constraint + * - skips implicit parameters; if result type depends on implicit parameter, + * replace with Wildcard. + * - converts non-dependent method types to the corresponding function types + * - dereferences parameterless method types + * - dereferences nullary method types provided the corresponding function type + * is not a subtype of the expected type. + * Note: We need to take account of the possibility of inserting a () argument list in normalization. Otherwise, a type with a + * def toString(): String + * member would not count as a valid solution for ?{toString: String}. This would then lead to an implicit + * insertion, with a nice explosion of inference search because of course every implicit result has some sort + * of toString method. The problem is solved by dereferencing nullary method types if the corresponding + * function type is not compatible with the prototype. + */ + def normalize(tp: Type, pt: Type)(implicit ctx: Context): Type = Stats.track("normalize") { + tp.widenSingleton match { + case poly: PolyType => normalize(constrained(poly).resultType, pt) + case mt: MethodType => + if (mt.isImplicit) + if (mt.isDependent) + mt.resultType.substParams(mt, mt.paramTypes.map(Function.const(WildcardType))) + else mt.resultType + else + if (mt.isDependent) tp + else { + val rt = normalize(mt.resultType, pt) + pt match { + case pt: IgnoredProto => mt + case pt: ApplyingProto => mt.derivedMethodType(mt.paramNames, mt.paramTypes, rt) + case _ => + val ft = defn.FunctionOf(mt.paramTypes, rt) + if (mt.paramTypes.nonEmpty || ft <:< pt) ft else rt + } + } + case et: ExprType => et.resultType + case _ => tp + } + } + + /** Approximate occurrences of parameter types and uninstantiated typevars + * by wildcard types. + */ + final def wildApprox(tp: Type, theMap: WildApproxMap = null)(implicit ctx: Context): Type = tp match { + case tp: NamedType => // default case, inlined for speed + if (tp.symbol.isStatic) tp + else tp.derivedSelect(wildApprox(tp.prefix, theMap)) + case tp: RefinedType => // default case, inlined for speed + tp.derivedRefinedType(wildApprox(tp.parent, theMap), tp.refinedName, wildApprox(tp.refinedInfo, theMap)) + case tp: TypeAlias => // default case, inlined for speed + tp.derivedTypeAlias(wildApprox(tp.alias, theMap)) + case tp @ PolyParam(poly, pnum) => + def unconstrainedApprox = WildcardType(wildApprox(poly.paramBounds(pnum)).bounds) + if (ctx.mode.is(Mode.TypevarsMissContext)) + unconstrainedApprox + else + ctx.typerState.constraint.entry(tp) match { + case bounds: TypeBounds => wildApprox(WildcardType(bounds)) + case NoType => unconstrainedApprox + case inst => wildApprox(inst) + } + case MethodParam(mt, pnum) => + WildcardType(TypeBounds.upper(wildApprox(mt.paramTypes(pnum)))) + case tp: TypeVar => + wildApprox(tp.underlying) + case tp @ HKApply(tycon, args) => + wildApprox(tycon) match { + case _: WildcardType => WildcardType // this ensures we get a * type + case tycon1 => tp.derivedAppliedType(tycon1, args.mapConserve(wildApprox(_))) + } + case tp: AndType => + val tp1a = wildApprox(tp.tp1) + val tp2a = wildApprox(tp.tp2) + def wildBounds(tp: Type) = + if (tp.isInstanceOf[WildcardType]) tp.bounds else TypeBounds.upper(tp) + if (tp1a.isInstanceOf[WildcardType] || tp2a.isInstanceOf[WildcardType]) + WildcardType(wildBounds(tp1a) & wildBounds(tp2a)) + else + tp.derivedAndType(tp1a, tp2a) + case tp: OrType => + val tp1a = wildApprox(tp.tp1) + val tp2a = wildApprox(tp.tp2) + if (tp1a.isInstanceOf[WildcardType] || tp2a.isInstanceOf[WildcardType]) + WildcardType(tp1a.bounds | tp2a.bounds) + else + tp.derivedOrType(tp1a, tp2a) + case tp: LazyRef => + WildcardType + case tp: SelectionProto => + tp.derivedSelectionProto(tp.name, wildApprox(tp.memberProto), NoViewsAllowed) + case tp: ViewProto => + tp.derivedViewProto(wildApprox(tp.argType), wildApprox(tp.resultType)) + case _: ThisType | _: BoundType | NoPrefix => // default case, inlined for speed + tp + case _ => + (if (theMap != null) theMap else new WildApproxMap).mapOver(tp) + } + + @sharable object AssignProto extends UncachedGroundType with MatchAlways + + private[ProtoTypes] class WildApproxMap(implicit ctx: Context) extends TypeMap { + def apply(tp: Type) = wildApprox(tp, this) + } + + /** Dummy tree to be used as an argument of a FunProto or ViewProto type */ + object dummyTreeOfType { + def apply(tp: Type): Tree = untpd.Literal(Constant(null)) withTypeUnchecked tp + def unapply(tree: Tree): Option[Type] = tree match { + case Literal(Constant(null)) => Some(tree.typeOpt) + case _ => None + } + } +} diff --git a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala new file mode 100644 index 000000000..2413c0c22 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala @@ -0,0 +1,108 @@ +package dotty.tools.dotc +package typer + +import core._ +import Contexts._ +import Types._ +import Symbols._ +import Decorators._ +import typer.ProtoTypes._ +import ast.{tpd, untpd} +import ast.Trees._ +import scala.util.control.NonFatal +import util.Positions.Position +import config.Printers.typr + +/** A version of Typer that keeps all symbols defined and referenced in a + * previously typed tree. + * + * All definition nodes keep their symbols. All leaf nodes for idents, selects, + * and TypeTrees keep their types. Indexing is a no-op. + * + * Otherwise, everything is as in Typer. + */ +class ReTyper extends Typer { + import tpd._ + + /** Checks that the given tree has been typed */ + protected def promote(tree: untpd.Tree)(implicit ctx: Context): tree.ThisTree[Type] = { + assert(tree.hasType, i"$tree ${tree.getClass} ${tree.uniqueId}") + tree.withType(tree.typeOpt) + } + + override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context): Tree = + promote(tree) + + override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { + assert(tree.hasType, tree) + val qual1 = typed(tree.qualifier, AnySelectionProto) + untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt) + } + + override def typedLiteral(tree: untpd.Literal)(implicit ctc: Context): Literal = + promote(tree) + + override def typedThis(tree: untpd.This)(implicit ctx: Context): Tree = + promote(tree) + + override def typedSuper(tree: untpd.Super, pt: Type)(implicit ctx: Context): Tree = + promote(tree) + + override def typedTypeTree(tree: untpd.TypeTree, pt: Type)(implicit ctx: Context): TypeTree = + promote(tree) + + override def typedBind(tree: untpd.Bind, pt: Type)(implicit ctx: Context): Bind = { + assert(tree.hasType) + val body1 = typed(tree.body, pt) + untpd.cpy.Bind(tree)(tree.name, body1).withType(tree.typeOpt) + } + + override def typedUnApply(tree: untpd.UnApply, selType: Type)(implicit ctx: Context): UnApply = { + val fun1 = typedExpr(tree.fun, AnyFunctionProto) + val implicits1 = tree.implicits.map(typedExpr(_)) + val patterns1 = tree.patterns.mapconserve(pat => typed(pat, pat.tpe)) + untpd.cpy.UnApply(tree)(fun1, implicits1, patterns1).withType(tree.tpe) + } + + override def localDummy(cls: ClassSymbol, impl: untpd.Template)(implicit ctx: Context) = impl.symbol + + override def retrieveSym(tree: untpd.Tree)(implicit ctx: Context): Symbol = tree.symbol + override def symbolOfTree(tree: untpd.Tree)(implicit ctx: Context): Symbol = tree.symbol + + override def localTyper(sym: Symbol) = this + + override def index(trees: List[untpd.Tree])(implicit ctx: Context) = ctx + + override def tryInsertApplyOrImplicit(tree: Tree, pt: ProtoType)(fallBack: (Tree, TyperState) => Tree)(implicit ctx: Context): Tree = + fallBack(tree, ctx.typerState) + + override def completeAnnotations(mdef: untpd.MemberDef, sym: Symbol)(implicit ctx: Context): Unit = () + + override def ensureConstrCall(cls: ClassSymbol, parents: List[Tree])(implicit ctx: Context): List[Tree] = + parents + + override def encodeName(tree: untpd.NameTree)(implicit ctx: Context) = tree + + override def handleUnexpectedFunType(tree: untpd.Apply, fun: Tree)(implicit ctx: Context): Tree = fun.tpe match { + case mt @ MethodType(_, formals) => + val args: List[Tree] = tree.args.zipWithConserve(formals)(typedExpr(_, _)).asInstanceOf[List[Tree]] + assignType(untpd.cpy.Apply(tree)(fun, args), fun, args) + case _ => + super.handleUnexpectedFunType(tree, fun) + } + + override def typedUnadapted(tree: untpd.Tree, pt: Type)(implicit ctx: Context) = + try super.typedUnadapted(tree, pt) + catch { + case NonFatal(ex) => + if (ctx.isAfterTyper) + println(i"exception while typing $tree of class ${tree.getClass} # ${tree.uniqueId}") + throw ex + } + + override def checkVariance(tree: Tree)(implicit ctx: Context) = () + override def inferView(from: Tree, to: Type)(implicit ctx: Context): Implicits.SearchResult = + Implicits.NoImplicitMatches + override def checkCanEqual(ltp: Type, rtp: Type, pos: Position)(implicit ctx: Context): Unit = () + override def inlineExpansion(mdef: DefDef)(implicit ctx: Context): List[Tree] = mdef :: Nil +} diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala new file mode 100644 index 000000000..46bdbf3b3 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -0,0 +1,1526 @@ +package dotty.tools.dotc +package typer + +import transform._ +import core._ +import config._ +import Symbols._, SymDenotations._, Types._, Contexts._, Decorators._, Flags._, Names._, NameOps._ +import StdNames._, Denotations._, Scopes._, Constants.Constant, SymUtils._ +import Annotations._ +import util.Positions._ +import scala.collection.{ mutable, immutable } +import ast._ +import Trees._ +import TreeTransforms._ +import util.DotClass +import scala.util.{Try, Success, Failure} +import config.{ScalaVersion, NoScalaVersion} +import Decorators._ +import typer.ErrorReporting._ +import DenotTransformers._ +import ValueClasses.isDerivedValueClass + +object RefChecks { + import tpd._ + + private def isDefaultGetter(name: Name): Boolean = + name.isTermName && name.asTermName.defaultGetterIndex >= 0 + + private val defaultMethodFilter = new NameFilter { + def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean = isDefaultGetter(name) + } + + /** Only one overloaded alternative is allowed to define default arguments */ + private def checkOverloadedRestrictions(clazz: Symbol)(implicit ctx: Context): Unit = { + // Using the default getters (such as methodName$default$1) as a cheap way of + // finding methods with default parameters. This way, we can limit the members to + // those with the DEFAULTPARAM flag, and infer the methods. Looking for the methods + // directly requires inspecting the parameter list of every one. That modification + // shaved 95% off the time spent in this method. + + for ( + defaultGetterClass <- List(clazz, clazz.companionModule.moduleClass); + if defaultGetterClass.isClass + ) { + val defaultGetterNames = defaultGetterClass.asClass.memberNames(defaultMethodFilter) + val defaultMethodNames = defaultGetterNames map (_.asTermName.defaultGetterToMethod) + + for (name <- defaultMethodNames) { + val methods = clazz.info.member(name).alternatives.map(_.symbol) + val haveDefaults = methods.filter(_.hasDefaultParams) + if (haveDefaults.length > 1) { + val owners = haveDefaults map (_.owner) + // constructors of different classes are allowed to have defaults + if (haveDefaults.exists(x => !x.isConstructor) || owners.distinct.size < haveDefaults.size) + ctx.error( + "in " + clazz + + ", multiple overloaded alternatives of " + haveDefaults.head + + " define default arguments" + ( + if (owners.forall(_ == clazz)) "." + else ".\nThe members with defaults are defined in " + owners.map(_.showLocated).mkString("", " and ", ".")), + clazz.pos) + } + } + } + + // Check for doomed attempt to overload applyDynamic + if (clazz derivesFrom defn.DynamicClass) { + for ((_, m1 :: m2 :: _) <- (clazz.info member nme.applyDynamic).alternatives groupBy (_.symbol.typeParams.length)) { + ctx.error("implementation restriction: applyDynamic cannot be overloaded except by methods with different numbers of type parameters, e.g. applyDynamic[T1](method: String)(arg: T1) and applyDynamic[T1, T2](method: String)(arg1: T1, arg2: T2)", + m1.symbol.pos) + } + } + } + + /** Check that self type of this class conforms to self types of parents. + * and required classes. + */ + private def checkParents(cls: Symbol)(implicit ctx: Context): Unit = cls.info match { + case cinfo: ClassInfo => + def checkSelfConforms(other: TypeRef, category: String, relation: String) = { + val otherSelf = other.givenSelfType.asSeenFrom(cls.thisType, other.classSymbol) + if (otherSelf.exists && !(cinfo.selfType <:< otherSelf)) + ctx.error(ex"$category: self type ${cinfo.selfType} of $cls does not conform to self type $otherSelf of $relation ${other.classSymbol}", cls.pos) + } + for (parent <- cinfo.classParents) + checkSelfConforms(parent, "illegal inheritance", "parent") + for (reqd <- cinfo.givenSelfType.classSymbols) + checkSelfConforms(reqd.typeRef, "missing requirement", "required") + case _ => + } + + /** Check that a class and its companion object to not both define + * a class or module with same name + */ + private def checkCompanionNameClashes(cls: Symbol)(implicit ctx: Context): Unit = + if (!(cls.owner is ModuleClass)) { + val other = cls.owner.linkedClass.info.decl(cls.name) + if (other.symbol.isClass) + ctx.error(s"name clash: ${cls.owner} defines $cls" + "\n" + + s"and its companion ${cls.owner.companionModule} also defines $other", + cls.pos) + } + + // Override checking ------------------------------------------------------------ + + /** 1. Check all members of class `clazz` for overriding conditions. + * That is for overriding member M and overridden member O: + * + * 1.1. M must have the same or stronger access privileges as O. + * 1.2. O must not be final. + * 1.3. O is deferred, or M has `override` modifier. + * 1.4. If O is stable, then so is M. + * // @M: LIFTED 1.5. Neither M nor O are a parameterized type alias + * 1.6. If O is a type alias, then M is an alias of O. + * 1.7. If O is an abstract type then + * 1.7.1 either M is an abstract type, and M's bounds are sharper than O's bounds. + * or M is a type alias or class which conforms to O's bounds. + * 1.7.2 higher-order type arguments must respect bounds on higher-order type parameters -- @M + * (explicit bounds and those implied by variance annotations) -- @see checkKindBounds + * 1.8. If O and M are values, then + * 1.8.1 M's type is a subtype of O's type, or + * 1.8.2 M is of type []S, O is of type ()T and S <: T, or + * 1.8.3 M is of type ()S, O is of type []T and S <: T, or + * 1.9. If M is a macro def, O cannot be deferred unless there's a concrete method overriding O. + * 1.10. If M is not a macro def, O cannot be a macro def. + * 2. Check that only abstract classes have deferred members + * 3. Check that concrete classes do not have deferred definitions + * that are not implemented in a subclass. + * 4. Check that every member with an `override` modifier + * overrides some other member. + * TODO check that classes are not overridden + * TODO This still needs to be cleaned up; the current version is a staright port of what was there + * before, but it looks too complicated and method bodies are far too large. + */ + private def checkAllOverrides(clazz: Symbol)(implicit ctx: Context): Unit = { + val self = clazz.thisType + var hasErrors = false + + case class MixinOverrideError(member: Symbol, msg: String) + + val mixinOverrideErrors = new mutable.ListBuffer[MixinOverrideError]() + + def printMixinOverrideErrors(): Unit = { + mixinOverrideErrors.toList match { + case List() => + case List(MixinOverrideError(_, msg)) => + ctx.error(msg, clazz.pos) + case MixinOverrideError(member, msg) :: others => + val others1 = others.map(_.member).filter(_.name != member.name).distinct + def othersMsg = { + val others1 = others.map(_.member) + .filter(_.name != member.name) + .map(_.show).distinct + if (others1.isEmpty) "" + else i";\n other members with override errors are:: $others1%, %" + } + ctx.error(msg + othersMsg, clazz.pos) + } + } + + def infoString(sym: Symbol) = infoString0(sym, sym.owner != clazz) + def infoStringWithLocation(sym: Symbol) = infoString0(sym, true) + + def infoString0(sym: Symbol, showLocation: Boolean) = { + val sym1 = sym.underlyingSymbol + def info = self.memberInfo(sym1) + i"${if (showLocation) sym1.showLocated else sym1}${ + if (sym1.isAliasType) i", which equals ${info.bounds.hi}" + else if (sym1.isAbstractType) i" with bounds$info" + else if (sym1.is(Module)) "" + else if (sym1.isTerm) i" of type $info" + else "" + }" + } + + /* Check that all conditions for overriding `other` by `member` + * of class `clazz` are met. + */ + def checkOverride(member: Symbol, other: Symbol): Unit = { + def memberTp = self.memberInfo(member) + def otherTp = self.memberInfo(other) + + ctx.debuglog("Checking validity of %s overriding %s".format(member.showLocated, other.showLocated)) + + def noErrorType = !memberTp.isErroneous && !otherTp.isErroneous + + def overrideErrorMsg(msg: String): String = { + val isConcreteOverAbstract = + (other.owner isSubClass member.owner) && other.is(Deferred) && !member.is(Deferred) + val addendum = + if (isConcreteOverAbstract) + ";\n (Note that %s is abstract,\n and is therefore overridden by concrete %s)".format( + infoStringWithLocation(other), + infoStringWithLocation(member)) + else if (ctx.settings.debug.value) + err.typeMismatchMsg(memberTp, otherTp) + else "" + + "overriding %s;\n %s %s%s".format( + infoStringWithLocation(other), infoString(member), msg, addendum) + } + + def emitOverrideError(fullmsg: String) = + if (!(hasErrors && member.is(Synthetic) && member.is(Module))) { + // suppress errors relating toi synthetic companion objects if other override + // errors (e.g. relating to the companion class) have already been reported. + if (member.owner == clazz) ctx.error(fullmsg, member.pos) + else mixinOverrideErrors += new MixinOverrideError(member, fullmsg) + hasErrors = true + } + + def overrideError(msg: String) = { + if (noErrorType) + emitOverrideError(overrideErrorMsg(msg)) + } + + def autoOverride(sym: Symbol) = + sym.is(Synthetic) && ( + desugar.isDesugaredCaseClassMethodName(member.name) || // such names are added automatically, can't have an override preset. + sym.is(Module)) // synthetic companion + + def overrideAccessError() = { + ctx.log(i"member: ${member.showLocated} ${member.flags}") // DEBUG + ctx.log(i"other: ${other.showLocated} ${other.flags}") // DEBUG + val otherAccess = (other.flags & AccessFlags).toString + overrideError("has weaker access privileges; it should be " + + (if (otherAccess == "") "public" else "at least " + otherAccess)) + } + + def compatibleTypes = + if (member.isType) { // intersection of bounds to refined types must be nonempty + member.is(BaseTypeArg) || + (memberTp frozen_<:< otherTp) || { + val jointBounds = (memberTp.bounds & otherTp.bounds).bounds + jointBounds.lo frozen_<:< jointBounds.hi + } + } + else + isDefaultGetter(member.name) || // default getters are not checked for compatibility + memberTp.overrides(otherTp) + + def domain(sym: Symbol): Set[Name] = sym.info.namedTypeParams.map(_.name) + + //Console.println(infoString(member) + " overrides " + infoString(other) + " in " + clazz);//DEBUG + + // return if we already checked this combination elsewhere + if (member.owner != clazz) { + def deferredCheck = member.is(Deferred) || !other.is(Deferred) + def subOther(s: Symbol) = s derivesFrom other.owner + def subMember(s: Symbol) = s derivesFrom member.owner + + if (subOther(member.owner) && deferredCheck) { + //Console.println(infoString(member) + " shadows1 " + infoString(other) " in " + clazz);//DEBUG + return + } + val parentSymbols = clazz.info.parents.map(_.typeSymbol) + if (parentSymbols exists (p => subOther(p) && subMember(p) && deferredCheck)) { + //Console.println(infoString(member) + " shadows2 " + infoString(other) + " in " + clazz);//DEBUG + return + } + if (parentSymbols forall (p => subOther(p) == subMember(p))) { + //Console.println(infoString(member) + " shadows " + infoString(other) + " in " + clazz);//DEBUG + return + } + } + + /* Is the intersection between given two lists of overridden symbols empty? */ + def intersectionIsEmpty(syms1: Iterator[Symbol], syms2: Iterator[Symbol]) = { + val set2 = syms2.toSet + !(syms1 exists (set2 contains _)) + } + + // o: public | protected | package-protected (aka java's default access) + // ^-may be overridden by member with access privileges-v + // m: public | public/protected | public/protected/package-protected-in-same-package-as-o + + if (member.is(Private)) // (1.1) + overrideError("has weaker access privileges; it should not be private") + + // todo: align accessibility implication checking with isAccessible in Contexts + val ob = other.accessBoundary(member.owner) + val mb = member.accessBoundary(member.owner) + def isOverrideAccessOK = ( + (member.flags & AccessFlags).isEmpty // member is public + || // - or - + (!other.is(Protected) || member.is(Protected)) && // if o is protected, so is m, and + (ob.isContainedIn(mb) || other.is(JavaProtected)) // m relaxes o's access boundary, + // or o is Java defined and protected (see #3946) + ) + if (!isOverrideAccessOK) { + overrideAccessError() + } else if (other.isClass) { + overrideError("cannot be used here - class definitions cannot be overridden") + } else if (!other.is(Deferred) && member.isClass) { + overrideError("cannot be used here - classes can only override abstract types") + } else if (other.isEffectivelyFinal) { // (1.2) + overrideError(i"cannot override final member ${other.showLocated}") + } else if (!other.is(Deferred) && + !isDefaultGetter(other.name) && + !member.isAnyOverride) { + // (*) Exclusion for default getters, fixes SI-5178. We cannot assign the Override flag to + // the default getter: one default getter might sometimes override, sometimes not. Example in comment on ticket. + if (autoOverride(member)) + member.setFlag(Override) + else if (member.owner != clazz && other.owner != clazz && !(other.owner derivesFrom member.owner)) + emitOverrideError( + clazz + " inherits conflicting members:\n " + + infoStringWithLocation(other) + " and\n " + infoStringWithLocation(member) + + "\n(Note: this can be resolved by declaring an override in " + clazz + ".)") + else + overrideError("needs `override' modifier") + } else if (other.is(AbsOverride) && other.isIncompleteIn(clazz) && !member.is(AbsOverride)) { + overrideError("needs `abstract override' modifiers") + } else if (member.is(Override) && other.is(Accessor) && + other.accessedFieldOrGetter.is(Mutable, butNot = Lazy)) { + // !?! this is not covered by the spec. We need to resolve this either by changing the spec or removing the test here. + // !!! is there a !?! convention? I'm !!!ing this to make sure it turns up on my searches. + if (!ctx.settings.overrideVars.value) + overrideError("cannot override a mutable variable") + } else if (member.isAnyOverride && + !(member.owner.thisType.baseClasses exists (_ isSubClass other.owner)) && + !member.is(Deferred) && !other.is(Deferred) && + intersectionIsEmpty(member.extendedOverriddenSymbols, other.extendedOverriddenSymbols)) { + overrideError("cannot override a concrete member without a third member that's overridden by both " + + "(this rule is designed to prevent ``accidental overrides'')") + } else if (other.isStable && !member.isStable) { // (1.4) + overrideError("needs to be a stable, immutable value") + } else if (member.is(ModuleVal) && !other.isRealMethod && !other.is(Deferred | Lazy)) { + overrideError("may not override a concrete non-lazy value") + } else if (member.is(Lazy, butNot = Module) && !other.isRealMethod && !other.is(Lazy)) { + overrideError("may not override a non-lazy value") + } else if (other.is(Lazy) && !other.isRealMethod && !member.is(Lazy)) { + overrideError("must be declared lazy to override a lazy value") + } else if (other.is(Deferred) && member.is(Macro) && member.extendedOverriddenSymbols.forall(_.is(Deferred))) { // (1.9) + overrideError("cannot be used here - term macros cannot override abstract methods") + } else if (other.is(Macro) && !member.is(Macro)) { // (1.10) + overrideError("cannot be used here - only term macros can override term macros") + } else if (!compatibleTypes) { + overrideError("has incompatible type" + err.whyNoMatchStr(memberTp, otherTp)) + } else if (member.isType && domain(member) != domain(other)) { + overrideError("has different named type parameters: "+ + i"[${domain(member).toList}%, %] instead of [${domain(other).toList}%, %]") + } else { + checkOverrideDeprecated() + } + } + + /* TODO enable; right now the annotation is scala-private, so cannot be seen + * here. + */ + def checkOverrideDeprecated() = { /* + if (other.hasDeprecatedOverridingAnnotation) { + val suffix = other.deprecatedOverridingMessage map (": " + _) getOrElse "" + val msg = s"overriding ${other.fullLocationString} is deprecated$suffix" + unit.deprecationWarning(member.pos, msg) + }*/ + } + + try { + val opc = new OverridingPairs.Cursor(clazz) + while (opc.hasNext) { + checkOverride(opc.overriding, opc.overridden) + opc.next() + } + } catch { + case ex: MergeError => + val addendum = ex.tp1 match { + case tp1: ClassInfo => + "\n(Note that having same-named member classes in types of a mixin composition is no longer allowed)" + case _ => "" + } + ctx.error(ex.getMessage + addendum, clazz.pos) + } + printMixinOverrideErrors() + + // Verifying a concrete class has nothing unimplemented. + if (!clazz.is(AbstractOrTrait)) { + val abstractErrors = new mutable.ListBuffer[String] + def abstractErrorMessage = + // a little formatting polish + if (abstractErrors.size <= 2) abstractErrors mkString " " + else abstractErrors.tail.mkString(abstractErrors.head + ":\n", "\n", "") + + def abstractClassError(mustBeMixin: Boolean, msg: String): Unit = { + def prelude = ( + if (clazz.isAnonymousClass || clazz.is(Module)) "object creation impossible" + else if (mustBeMixin) clazz + " needs to be a mixin" + else clazz + " needs to be abstract") + ", since" + + if (abstractErrors.isEmpty) abstractErrors ++= List(prelude, msg) + else abstractErrors += msg + } + + def hasJavaErasedOverriding(sym: Symbol): Boolean = + !ctx.erasurePhase.exists || // can't do the test, assume the best + ctx.atPhase(ctx.erasurePhase.next) { implicit ctx => + clazz.info.nonPrivateMember(sym.name).hasAltWith { alt => + alt.symbol.is(JavaDefined, butNot = Deferred) && + !sym.owner.derivesFrom(alt.symbol.owner) && + alt.matches(sym) + } + } + + def ignoreDeferred(member: SingleDenotation) = + member.isType || + member.symbol.is(SuperAccessor) || // not yet synthesized + member.symbol.is(JavaDefined) && hasJavaErasedOverriding(member.symbol) + + // 2. Check that only abstract classes have deferred members + def checkNoAbstractMembers(): Unit = { + // Avoid spurious duplicates: first gather any missing members. + val missing = clazz.thisType.abstractTermMembers.filterNot(ignoreDeferred) + // Group missing members by the name of the underlying symbol, + // to consolidate getters and setters. + val grouped: Map[Name, Seq[SingleDenotation]] = missing groupBy (_.symbol.underlyingSymbol.name) + // Dotty deviation: Added type annotation for `grouped`. + // The inferred type is Map[Symbol#ThisName, Seq[SingleDenotation]] + // but then the definition of isMultiple fails with an error: + // RefChecks.scala:379: error: type mismatch: + // found : underlying.ThisName + // required: dotty.tools.dotc.core.Symbols.Symbol#ThisName + // + // val isMultiple = grouped.getOrElse(underlying.name(ctx), Nil).size > 1 + // ^ + // As far as I can see, the complaint is correct, even under the + // old reading where Symbol#ThisName means x.ThisName forSome { val x } + + val missingMethods = grouped.toList flatMap { + case (name, syms) => + val withoutSetters = syms filterNot (_.symbol.isSetter) + if (withoutSetters.nonEmpty) withoutSetters else syms + } + + def stubImplementations: List[String] = { + // Grouping missing methods by the declaring class + val regrouped = missingMethods.groupBy(_.symbol.owner).toList + def membersStrings(members: List[SingleDenotation]) = + members.sortBy(_.symbol.name.toString).map(_.showDcl + " = ???") + + if (regrouped.tail.isEmpty) + membersStrings(regrouped.head._2) + else (regrouped.sortBy("" + _._1.name) flatMap { + case (owner, members) => + ("// Members declared in " + owner.fullName) +: membersStrings(members) :+ "" + }).init + } + + // If there are numerous missing methods, we presume they are aware of it and + // give them a nicely formatted set of method signatures for implementing. + if (missingMethods.size > 1) { + abstractClassError(false, "it has " + missingMethods.size + " unimplemented members.") + val preface = + """|/** As seen from %s, the missing signatures are as follows. + | * For convenience, these are usable as stub implementations. + | */ + |""".stripMargin.format(clazz) + abstractErrors += stubImplementations.map(" " + _ + "\n").mkString(preface, "", "") + return + } + + for (member <- missing) { + val memberSym = member.symbol + def undefined(msg: String) = + abstractClassError(false, s"${member.showDcl} is not defined $msg") + val underlying = memberSym.underlyingSymbol + + // Give a specific error message for abstract vars based on why it fails: + // It could be unimplemented, have only one accessor, or be uninitialized. + if (underlying.is(Mutable)) { + val isMultiple = grouped.getOrElse(underlying.name(ctx), Nil).size > 1 + + // If both getter and setter are missing, squelch the setter error. + if (memberSym.isSetter && isMultiple) () + else undefined( + if (memberSym.isSetter) "\n(Note that an abstract var requires a setter in addition to the getter)" + else if (memberSym.isGetter && !isMultiple) "\n(Note that an abstract var requires a getter in addition to the setter)" + else err.abstractVarMessage(memberSym)) + } else if (underlying.is(Method)) { + // If there is a concrete method whose name matches the unimplemented + // abstract method, and a cursory examination of the difference reveals + // something obvious to us, let's make it more obvious to them. + val abstractParams = underlying.info.firstParamTypes + val matchingName = clazz.info.nonPrivateMember(underlying.name).alternatives + val matchingArity = matchingName filter { m => + !m.symbol.is(Deferred) && + m.info.firstParamTypes.length == abstractParams.length + } + + matchingArity match { + // So far so good: only one candidate method + case concrete :: Nil => + val mismatches = + abstractParams.zip(concrete.info.firstParamTypes) + .filterNot { case (x, y) => x =:= y } + mismatches match { + // Only one mismatched parameter: say something useful. + case (pa, pc) :: Nil => + val abstractSym = pa.typeSymbol + val concreteSym = pc.typeSymbol + def subclassMsg(c1: Symbol, c2: Symbol) = + s": ${c1.showLocated} is a subclass of ${c2.showLocated}, but method parameter types must match exactly." + val addendum = + if (abstractSym == concreteSym) { + val paArgs = pa.argInfos + val pcArgs = pc.argInfos + val paConstr = pa.withoutArgs(paArgs) + val pcConstr = pc.withoutArgs(pcArgs) + (paConstr, pcConstr) match { + case (TypeRef(pre1, _), TypeRef(pre2, _)) => + if (pre1 =:= pre2) ": their type parameters differ" + else ": their prefixes (i.e. enclosing instances) differ" + case _ => + "" + } + } else if (abstractSym isSubClass concreteSym) + subclassMsg(abstractSym, concreteSym) + else if (concreteSym isSubClass abstractSym) + subclassMsg(concreteSym, abstractSym) + else "" + + undefined(s"\n(Note that ${pa.show} does not match ${pc.show}$addendum)") + case xs => + undefined(s"\n(The class implements a member with a different type: ${concrete.showDcl})") + } + case Nil => + undefined("") + case concretes => + undefined(s"\n(The class implements members with different types: ${concretes.map(_.showDcl)}%\n %)") + } + } else undefined("") + } + } + + // 3. Check that concrete classes do not have deferred definitions + // that are not implemented in a subclass. + // Note that this is not the same as (2); In a situation like + // + // class C { def m: Int = 0} + // class D extends C { def m: Int } + // + // (3) is violated but not (2). + def checkNoAbstractDecls(bc: Symbol): Unit = { + for (decl <- bc.info.decls) { + if (decl.is(Deferred) && !ignoreDeferred(decl)) { + val impl = decl.matchingMember(clazz.thisType) + if (impl == NoSymbol || (decl.owner isSubClass impl.owner)) { + val impl1 = clazz.thisType.nonPrivateMember(decl.name) // DEBUG + ctx.log(i"${impl1}: ${impl1.info}") // DEBUG + ctx.log(i"${clazz.thisType.memberInfo(decl)}") // DEBUG + abstractClassError(false, "there is a deferred declaration of " + infoString(decl) + + " which is not implemented in a subclass" + err.abstractVarMessage(decl)) + } + } + } + if (bc.asClass.superClass.is(Abstract)) + checkNoAbstractDecls(bc.asClass.superClass) + } + + checkNoAbstractMembers() + if (abstractErrors.isEmpty) + checkNoAbstractDecls(clazz) + + if (abstractErrors.nonEmpty) + ctx.error(abstractErrorMessage, clazz.pos) + } else if (clazz.is(Trait) && !(clazz derivesFrom defn.AnyValClass)) { + // For non-AnyVal classes, prevent abstract methods in interfaces that override + // final members in Object; see #4431 + for (decl <- clazz.info.decls) { + // Have to use matchingSymbol, not a method involving overridden symbols, + // because the scala type system understands that an abstract method here does not + // override a concrete method in Object. The jvm, however, does not. + val overridden = decl.matchingDecl(defn.ObjectClass, defn.ObjectType) + if (overridden.is(Final)) + ctx.error("trait cannot redefine final method from class AnyRef", decl.pos) + } + } + + /* Returns whether there is a symbol declared in class `inclazz` + * (which must be different from `clazz`) whose name and type + * seen as a member of `class.thisType` matches `member`'s. + */ + def hasMatchingSym(inclazz: Symbol, member: Symbol): Boolean = { + + def isSignatureMatch(sym: Symbol) = !sym.isTerm || + clazz.thisType.memberInfo(sym).matchesLoosely(member.info) + + /* The rules for accessing members which have an access boundary are more + * restrictive in java than scala. Since java has no concept of package nesting, + * a member with "default" (package-level) access can only be accessed by members + * in the exact same package. Example: + * + * package a.b; + * public class JavaClass { void foo() { } } + * + * The member foo() can be accessed only from members of package a.b, and not + * nested packages like a.b.c. In the analogous scala class: + * + * package a.b + * class ScalaClass { private[b] def foo() = () } + * + * The member IS accessible to classes in package a.b.c. The javaAccessCheck logic + * is restricting the set of matching signatures according to the above semantics. + */ + def javaAccessCheck(sym: Symbol) = ( + !inclazz.is(JavaDefined) // not a java defined member + || !sym.privateWithin.exists // no access boundary + || sym.is(Protected) // marked protected in java, thus accessible to subclasses + || sym.privateWithin == member.enclosingPackageClass // exact package match + ) + def classDecls = inclazz.info.nonPrivateDecl(member.name) + + (inclazz != clazz) && + classDecls.hasAltWith(d => isSignatureMatch(d.symbol) && javaAccessCheck(d.symbol)) + } + + // 4. Check that every defined member with an `override` modifier overrides some other member. + for (member <- clazz.info.decls) + if (member.isAnyOverride && !(clazz.thisType.baseClasses exists (hasMatchingSym(_, member)))) { + // for (bc <- clazz.info.baseClasses.tail) Console.println("" + bc + " has " + bc.info.decl(member.name) + ":" + bc.info.decl(member.name).tpe);//DEBUG + + val nonMatching = clazz.info.member(member.name).altsWith(alt => alt.owner != clazz && !alt.is(Final)) + def issueError(suffix: String) = + ctx.error(i"$member overrides nothing$suffix", member.pos) + nonMatching match { + case Nil => + issueError("") + case ms => + val superSigs = ms.map(_.showDcl).mkString("\n") + issueError(s".\nNote: the super classes of ${member.owner} contain the following, non final members named ${member.name}:\n${superSigs}") + } + member.resetFlag(Override) + member.resetFlag(AbsOverride) + } + } + + // Note: if a symbol has both @deprecated and @migration annotations and both + // warnings are enabled, only the first one checked here will be emitted. + // I assume that's a consequence of some code trying to avoid noise by suppressing + // warnings after the first, but I think it'd be better if we didn't have to + // arbitrarily choose one as more important than the other. + private def checkUndesiredProperties(sym: Symbol, pos: Position)(implicit ctx: Context): Unit = { + // If symbol is deprecated, and the point of reference is not enclosed + // in either a deprecated member or a scala bridge method, issue a warning. + if (sym.isDeprecated && !ctx.owner.ownersIterator.exists(_.isDeprecated)) { + ctx.deprecationWarning("%s%s is deprecated%s".format( + sym, sym.showLocated, sym.deprecationMessage map (": " + _) getOrElse "", pos)) + } + // Similar to deprecation: check if the symbol is marked with @migration + // indicating it has changed semantics between versions. + if (sym.hasAnnotation(defn.MigrationAnnot) && ctx.settings.Xmigration.value != NoScalaVersion) { + val symVersion: scala.util.Try[ScalaVersion] = sym.migrationVersion.get + val changed = symVersion match { + case scala.util.Success(v) => + ctx.settings.Xmigration.value < v + case Failure(ex) => + ctx.warning(s"${sym.showLocated} has an unparsable version number: ${ex.getMessage()}", pos) + false + } + if (changed) + ctx.warning(s"${sym.showLocated} has changed semantics in version $symVersion:\n${sym.migrationMessage.get}") + } + /* (Not enabled yet) + * See an explanation of compileTimeOnly in its scaladoc at scala.annotation.compileTimeOnly. + * + if (sym.isCompileTimeOnly) { + def defaultMsg = + sm"""Reference to ${sym.fullLocationString} should not have survived past type checking, + |it should have been processed and eliminated during expansion of an enclosing macro.""" + // The getOrElse part should never happen, it's just here as a backstop. + ctx.error(sym.compileTimeOnlyMessage getOrElse defaultMsg, pos) + }*/ + } + + /** Check that a deprecated val or def does not override a + * concrete, non-deprecated method. If it does, then + * deprecation is meaningless. + */ + private def checkDeprecatedOvers(tree: Tree)(implicit ctx: Context): Unit = { + val symbol = tree.symbol + if (symbol.isDeprecated) { + val concrOvers = + symbol.allOverriddenSymbols.filter(sym => + !sym.isDeprecated && !sym.is(Deferred)) + if (!concrOvers.isEmpty) + ctx.deprecationWarning( + symbol.toString + " overrides concrete, non-deprecated symbol(s):" + + concrOvers.map(_.name.decode).mkString(" ", ", ", ""), tree.pos) + } + } + + /** Verify classes extending AnyVal meet the requirements */ + private def checkDerivedValueClass(clazz: Symbol, stats: List[Tree])(implicit ctx: Context) = { + def checkValueClassMember(stat: Tree) = stat match { + case _: ValDef if !stat.symbol.is(ParamAccessor) => + ctx.error(s"value class may not define non-parameter field", stat.pos) + case _: DefDef if stat.symbol.isConstructor => + ctx.error(s"value class may not define secondary constructor", stat.pos) + case _: MemberDef | _: Import | EmptyTree => + // ok + case _ => + ctx.error(s"value class may not contain initialization statements", stat.pos) + } + if (isDerivedValueClass(clazz)) { + if (clazz.is(Trait)) + ctx.error("Only classes (not traits) are allowed to extend AnyVal", clazz.pos) + if (clazz.is(Abstract)) + ctx.error("`abstract' modifier cannot be used with value classes", clazz.pos) + if (!clazz.isStatic) + ctx.error(s"value class may not be a ${if (clazz.owner.isTerm) "local class" else "member of another class"}", clazz.pos) + else { + val clParamAccessors = clazz.asClass.paramAccessors.filter(sym => sym.isTerm && !sym.is(Method)) + clParamAccessors match { + case List(param) => + if (param.is(Mutable)) + ctx.error("value class parameter must not be a var", param.pos) + case _ => + ctx.error("value class needs to have exactly one val parameter", clazz.pos) + } + } + stats.foreach(checkValueClassMember) + } + } + + type LevelAndIndex = immutable.Map[Symbol, (LevelInfo, Int)] + + class OptLevelInfo extends DotClass { + def levelAndIndex: LevelAndIndex = Map() + def enterReference(sym: Symbol, pos: Position): Unit = () + } + + /** A class to help in forward reference checking */ + class LevelInfo(outerLevelAndIndex: LevelAndIndex, stats: List[Tree])(implicit ctx: Context) + extends OptLevelInfo { + override val levelAndIndex: LevelAndIndex = + ((outerLevelAndIndex, 0) /: stats) {(mi, stat) => + val (m, idx) = mi + val m1 = stat match { + case stat: MemberDef => m.updated(stat.symbol, (this, idx)) + case _ => m + } + (m1, idx + 1) + }._1 + var maxIndex: Int = Int.MinValue + var refPos: Position = _ + var refSym: Symbol = _ + + override def enterReference(sym: Symbol, pos: Position): Unit = + if (sym.exists && sym.owner.isTerm) + levelAndIndex.get(sym) match { + case Some((level, idx)) if (level.maxIndex < idx) => + level.maxIndex = idx + level.refPos = pos + level.refSym = sym + case _ => + } + } + + val NoLevelInfo = new OptLevelInfo() +} +import RefChecks._ + +/** Post-attribution checking and transformation, which fulfills the following roles + * + * 1. This phase performs the following checks. + * + * - only one overloaded alternative defines default arguments + * - applyDynamic methods are not overloaded + * - all overrides conform to rules laid down by `checkAllOverrides`. + * - any value classes conform to rules laid down by `checkDerivedValueClass`. + * - this(...) constructor calls do not forward reference other definitions in their block (not even lazy vals). + * - no forward reference in a local block jumps over a non-lazy val definition. + * - a class and its companion object do not both define a class or module with the same name. + * + * 2. It warns about references to symbols labeled deprecated or migration. + + * 3. It performs the following transformations: + * + * - if (true) A else B --> A + * if (false) A else B --> B + * - macro definitions are eliminated. + * + * 4. It makes members not private where necessary. The following members + * cannot be private in the Java model: + * - term members of traits + * - the primary constructor of a value class + * - the parameter accessor of a value class + * - members accessed from an inner or companion class. + * All these members are marked as NotJavaPrivate. + * Unlike in Scala 2.x not-private members keep their name. It is + * up to the backend to find a unique expanded name for them. The + * rationale to do name changes that late is that they are very fragile. + + * todo: But RefChecks is not done yet. It's still a somewhat dirty port from the Scala 2 version. + * todo: move untrivial logic to their own mini-phases + */ +class RefChecks extends MiniPhase { thisTransformer => + + import tpd._ + + override def phaseName: String = "refchecks" + + val treeTransform = new Transform(NoLevelInfo) + + class Transform(currentLevel: RefChecks.OptLevelInfo = RefChecks.NoLevelInfo) extends TreeTransform { + def phase = thisTransformer + + override def prepareForStats(trees: List[Tree])(implicit ctx: Context) = { + // println(i"preparing for $trees%; %, owner = ${ctx.owner}") + if (ctx.owner.isTerm) new Transform(new LevelInfo(currentLevel.levelAndIndex, trees)) + else this + } + + override def transformStats(trees: List[Tree])(implicit ctx: Context, info: TransformerInfo): List[Tree] = trees + + override def transformValDef(tree: ValDef)(implicit ctx: Context, info: TransformerInfo) = { + checkDeprecatedOvers(tree) + val sym = tree.symbol + if (sym.exists && sym.owner.isTerm && !sym.is(Lazy)) + currentLevel.levelAndIndex.get(sym) match { + case Some((level, symIdx)) if symIdx < level.maxIndex => + ctx.debuglog("refsym = " + level.refSym) + ctx.error(s"forward reference extends over definition of $sym", level.refPos) + case _ => + } + tree + } + + override def transformDefDef(tree: DefDef)(implicit ctx: Context, info: TransformerInfo) = { + checkDeprecatedOvers(tree) + if (tree.symbol is Macro) EmptyTree else tree + } + + override def transformTemplate(tree: Template)(implicit ctx: Context, info: TransformerInfo) = try { + val cls = ctx.owner + checkOverloadedRestrictions(cls) + checkParents(cls) + checkCompanionNameClashes(cls) + checkAllOverrides(cls) + checkDerivedValueClass(cls, tree.body) + tree + } catch { + case ex: MergeError => + ctx.error(ex.getMessage, tree.pos) + tree + } + + override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo) = { + checkUndesiredProperties(tree.symbol, tree.pos) + currentLevel.enterReference(tree.symbol, tree.pos) + tree + } + + override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo) = { + checkUndesiredProperties(tree.symbol, tree.pos) + tree + } + + override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo) = { + if (isSelfConstrCall(tree)) { + assert(currentLevel.isInstanceOf[LevelInfo], ctx.owner + "/" + i"$tree") + val level = currentLevel.asInstanceOf[LevelInfo] + if (level.maxIndex > 0) { + // An implementation restriction to avoid VerifyErrors and lazyvals mishaps; see SI-4717 + ctx.debuglog("refsym = " + level.refSym) + ctx.error("forward reference not allowed from self constructor invocation", level.refPos) + } + } + tree + } + + override def transformIf(tree: If)(implicit ctx: Context, info: TransformerInfo) = + tree.cond.tpe match { + case ConstantType(value) => if (value.booleanValue) tree.thenp else tree.elsep + case _ => tree + } + + override def transformNew(tree: New)(implicit ctx: Context, info: TransformerInfo) = { + currentLevel.enterReference(tree.tpe.typeSymbol, tree.pos) + tree + } + + override def transformTypeApply(tree: tpd.TypeApply)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { + tree.fun match { + case fun@Select(qual, selector) => + val sym = tree.symbol + + if (sym == defn.Any_isInstanceOf) { + val argType = tree.args.head.tpe + val qualCls = qual.tpe.widen.classSymbol + val argCls = argType.classSymbol + if (qualCls.isPrimitiveValueClass && !argCls.isPrimitiveValueClass) ctx.error("isInstanceOf cannot test if value types are references", tree.pos) + } + case _ => + } + tree + } + } +} + +/* todo: rewrite and re-enable + +// Comparison checking ------------------------------------------------------- + + object normalizeAll extends TypeMap { + def apply(tp: Type) = mapOver(tp).normalize + } + + def checkImplicitViewOptionApply(pos: Position, fn: Tree, args: List[Tree]): Unit = if (settings.lint) (fn, args) match { + case (tap@TypeApply(fun, targs), List(view: ApplyImplicitView)) if fun.symbol == currentRun.runDefinitions.Option_apply => + unit.warning(pos, s"Suspicious application of an implicit view (${view.fun}) in the argument to Option.apply.") // SI-6567 + case _ => + } + + private def isObjectOrAnyComparisonMethod(sym: Symbol) = sym match { + case Object_eq | Object_ne | Object_== | Object_!= | Any_== | Any_!= => true + case _ => false + } + /** Check the sensibility of using the given `equals` to compare `qual` and `other`. */ + private def checkSensibleEquals(pos: Position, qual: Tree, name: Name, sym: Symbol, other: Tree) = { + def isReferenceOp = sym == Object_eq || sym == Object_ne + def isNew(tree: Tree) = tree match { + case Function(_, _) | Apply(Select(New(_), nme.CONSTRUCTOR), _) => true + case _ => false + } + def underlyingClass(tp: Type): Symbol = { + val sym = tp.widen.typeSymbol + if (sym.isAbstractType) underlyingClass(sym.info.bounds.hi) + else sym + } + val actual = underlyingClass(other.tpe) + val receiver = underlyingClass(qual.tpe) + def onTrees[T](f: List[Tree] => T) = f(List(qual, other)) + def onSyms[T](f: List[Symbol] => T) = f(List(receiver, actual)) + + // @MAT normalize for consistency in error message, otherwise only part is normalized due to use of `typeSymbol` + def typesString = normalizeAll(qual.tpe.widen)+" and " + normalizeAll(other.tpe.widen) + + /* Symbols which limit the warnings we can issue since they may be value types */ + val isMaybeValue = Set[Symbol](AnyClass, AnyRefClass, AnyValClass, ObjectClass, ComparableClass, JavaSerializableClass) + + // Whether def equals(other: Any) has known behavior: it is the default + // inherited from java.lang.Object, or it is a synthetically generated + // case equals. TODO - more cases are warnable if the target is a synthetic + // equals. + def isUsingWarnableEquals = { + val m = receiver.info.member(nme.equals_) + ((m == Object_equals) || (m == Any_equals) || isMethodCaseEquals(m)) + } + def isMethodCaseEquals(m: Symbol) = m.isSynthetic && m.owner.isCase + def isCaseEquals = isMethodCaseEquals(receiver.info.member(nme.equals_)) + // Whether this == or != is one of those defined in Any/AnyRef or an overload from elsewhere. + def isUsingDefaultScalaOp = sym == Object_== || sym == Object_!= || sym == Any_== || sym == Any_!= + def haveSubclassRelationship = (actual isSubClass receiver) || (receiver isSubClass actual) + + // Whether the operands+operator represent a warnable combo (assuming anyrefs) + // Looking for comparisons performed with ==/!= in combination with either an + // equals method inherited from Object or a case class synthetic equals (for + // which we know the logic.) + def isWarnable = isReferenceOp || (isUsingDefaultScalaOp && isUsingWarnableEquals) + def isEitherNullable = (NullTpe <:< receiver.info) || (NullTpe <:< actual.info) + def isEitherValueClass = actual.isDerivedValueClass || receiver.isDerivedValueClass + def isBoolean(s: Symbol) = unboxedValueClass(s) == BooleanClass + def isUnit(s: Symbol) = unboxedValueClass(s) == UnitClass + def isNumeric(s: Symbol) = isNumericValueClass(unboxedValueClass(s)) || isAnyNumber(s) + def isScalaNumber(s: Symbol) = s isSubClass ScalaNumberClass + def isJavaNumber(s: Symbol) = s isSubClass JavaNumberClass + // includes java.lang.Number if appropriate [SI-5779] + def isAnyNumber(s: Symbol) = isScalaNumber(s) || isJavaNumber(s) + def isMaybeAnyValue(s: Symbol) = isPrimitiveValueClass(unboxedValueClass(s)) || isMaybeValue(s) + // used to short-circuit unrelatedTypes check if both sides are special + def isSpecial(s: Symbol) = isMaybeAnyValue(s) || isAnyNumber(s) + val nullCount = onSyms(_ filter (_ == NullClass) size) + def isNonsenseValueClassCompare = ( + !haveSubclassRelationship + && isUsingDefaultScalaOp + && isEitherValueClass + && !isCaseEquals + ) + + // Have we already determined that the comparison is non-sensible? I mean, non-sensical? + var isNonSensible = false + + def nonSensibleWarning(what: String, alwaysEqual: Boolean) = { + val msg = alwaysEqual == (name == nme.EQ || name == nme.eq) + unit.warning(pos, s"comparing $what using `${name.decode}' will always yield $msg") + isNonSensible = true + } + def nonSensible(pre: String, alwaysEqual: Boolean) = + nonSensibleWarning(s"${pre}values of types $typesString", alwaysEqual) + def nonSensiblyEq() = nonSensible("", alwaysEqual = true) + def nonSensiblyNeq() = nonSensible("", alwaysEqual = false) + def nonSensiblyNew() = nonSensibleWarning("a fresh object", alwaysEqual = false) + + def unrelatedMsg = name match { + case nme.EQ | nme.eq => "never compare equal" + case _ => "always compare unequal" + } + def unrelatedTypes() = if (!isNonSensible) { + val weaselWord = if (isEitherValueClass) "" else " most likely" + unit.warning(pos, s"$typesString are unrelated: they will$weaselWord $unrelatedMsg") + } + + if (nullCount == 2) // null == null + nonSensiblyEq() + else if (nullCount == 1) { + if (onSyms(_ exists isPrimitiveValueClass)) // null == 5 + nonSensiblyNeq() + else if (onTrees( _ exists isNew)) // null == new AnyRef + nonSensiblyNew() + } + else if (isBoolean(receiver)) { + if (!isBoolean(actual) && !isMaybeValue(actual)) // true == 5 + nonSensiblyNeq() + } + else if (isUnit(receiver)) { + if (isUnit(actual)) // () == () + nonSensiblyEq() + else if (!isUnit(actual) && !isMaybeValue(actual)) // () == "abc" + nonSensiblyNeq() + } + else if (isNumeric(receiver)) { + if (!isNumeric(actual)) + if (isUnit(actual) || isBoolean(actual) || !isMaybeValue(actual)) // 5 == "abc" + nonSensiblyNeq() + } + else if (isWarnable && !isCaseEquals) { + if (isNew(qual)) // new X == y + nonSensiblyNew() + else if (isNew(other) && (receiver.isEffectivelyFinal || isReferenceOp)) // object X ; X == new Y + nonSensiblyNew() + else if (receiver.isEffectivelyFinal && !(receiver isSubClass actual) && !actual.isRefinementClass) { // object X, Y; X == Y + if (isEitherNullable) + nonSensible("non-null ", false) + else + nonSensiblyNeq() + } + } + + // warn if one but not the other is a derived value class + // this is especially important to enable transitioning from + // regular to value classes without silent failures. + if (isNonsenseValueClassCompare) + unrelatedTypes() + // possibleNumericCount is insufficient or this will warn on e.g. Boolean == j.l.Boolean + else if (isWarnable && nullCount == 0 && !(isSpecial(receiver) && isSpecial(actual))) { + // better to have lubbed and lost + def warnIfLubless(): Unit = { + val common = global.lub(List(actual.tpe, receiver.tpe)) + if (ObjectTpe <:< common) + unrelatedTypes() + } + // warn if actual has a case parent that is not same as receiver's; + // if actual is not a case, then warn if no common supertype, as below + if (isCaseEquals) { + def thisCase = receiver.info.member(nme.equals_).owner + actual.info.baseClasses.find(_.isCase) match { + case Some(p) if p != thisCase => nonSensible("case class ", false) + case None => + // stronger message on (Some(1) == None) + //if (receiver.isCase && receiver.isEffectivelyFinal && !(receiver isSubClass actual)) nonSensiblyNeq() + //else + // if a class, it must be super to thisCase (and receiver) since not <: thisCase + if (!actual.isTrait && !(receiver isSubClass actual)) nonSensiblyNeq() + else if (!haveSubclassRelationship) warnIfLubless() + case _ => + } + } + // warn only if they have no common supertype below Object + else if (!haveSubclassRelationship) { + warnIfLubless() + } + } + } + /** Sensibility check examines flavors of equals. */ + def checkSensible(pos: Position, fn: Tree, args: List[Tree]) = fn match { + case Select(qual, name @ (nme.EQ | nme.NE | nme.eq | nme.ne)) if args.length == 1 && isObjectOrAnyComparisonMethod(fn.symbol) => + checkSensibleEquals(pos, qual, name, fn.symbol, args.head) + case _ => + } +*/ + +/* --------------- Overflow ------------------------------------------------- + * + + def accessFlagsToString(sym: Symbol) = flagsToString( + sym getFlag (PRIVATE | PROTECTED), + if (sym.hasAccessBoundary) "" + sym.privateWithin.name else "" + ) + + def overridesTypeInPrefix(tp1: Type, tp2: Type, prefix: Type): Boolean = (tp1.dealiasWiden, tp2.dealiasWiden) match { + case (MethodType(List(), rtp1), NullaryMethodType(rtp2)) => + rtp1 <:< rtp2 + case (NullaryMethodType(rtp1), MethodType(List(), rtp2)) => + rtp1 <:< rtp2 + case (TypeRef(_, sym, _), _) if sym.isModuleClass => + overridesTypeInPrefix(NullaryMethodType(tp1), tp2, prefix) + case _ => + def classBoundAsSeen(tp: Type) = tp.typeSymbol.classBound.asSeenFrom(prefix, tp.typeSymbol.owner) + + (tp1 <:< tp2) || ( // object override check + tp1.typeSymbol.isModuleClass && tp2.typeSymbol.isModuleClass && { + val cb1 = classBoundAsSeen(tp1) + val cb2 = classBoundAsSeen(tp2) + (cb1 <:< cb2) && { + log("Allowing %s to override %s because %s <:< %s".format(tp1, tp2, cb1, cb2)) + true + } + } + ) + } + private def checkTypeRef(tp: Type, tree: Tree, skipBounds: Boolean)(implicit ctx: Context) = tp match { + case TypeRef(pre, sym, args) => + tree match { + case tt: TypeTree if tt.original == null => // SI-7783 don't warn about inferred types + // FIXME: reconcile this check with one in resetAttrs + case _ => checkUndesiredProperties(sym, tree.pos) + } + if (sym.isJavaDefined) + sym.typeParams foreach (_.cookJavaRawInfo()) + if (!tp.isHigherKinded && !skipBounds) + checkBounds(tree, pre, sym.owner, sym.typeParams, args) + case _ => + } + + private def checkTypeRefBounds(tp: Type, tree: Tree) = { + var skipBounds = false + tp match { + case AnnotatedType(ann :: Nil, underlying) if ann.symbol == UncheckedBoundsClass => + skipBounds = true + underlying + case TypeRef(pre, sym, args) => + if (!tp.isHigherKinded && !skipBounds) + checkBounds(tree, pre, sym.owner, sym.typeParams, args) + tp + case _ => + tp + } + } + + private def checkAnnotations(tpes: List[Type], tree: Tree) = tpes foreach { tp => + checkTypeRef(tp, tree, skipBounds = false) + checkTypeRefBounds(tp, tree) + } + private def doTypeTraversal(tree: Tree)(f: Type => Unit) = if (!inPattern) tree.tpe foreach f + + private def applyRefchecksToAnnotations(tree: Tree)(implicit ctx: Context): Unit = { + def applyChecks(annots: List[Annotation]) = { + checkAnnotations(annots map (_.atp), tree) + transformTrees(annots flatMap (_.args)) + } + + tree match { + case m: MemberDef => + val sym = m.symbol + applyChecks(sym.annotations) + // validate implicitNotFoundMessage + analyzer.ImplicitNotFoundMsg.check(sym) foreach { warn => + unit.warning(tree.pos, f"Invalid implicitNotFound message for ${sym}%s${sym.locationString}%s:%n$warn") + } + + case tpt@TypeTree() => + if (tpt.original != null) { + tpt.original foreach { + case dc@TypeTreeWithDeferredRefCheck() => + applyRefchecksToAnnotations(dc.check()) // #2416 + case _ => + } + } + + doTypeTraversal(tree) { + case tp @ AnnotatedType(annots, _) => + applyChecks(annots) + case tp => + } + case _ => + } + } + + private def transformCaseApply(tree: Tree, ifNot: => Unit) = { + val sym = tree.symbol + + def isClassTypeAccessible(tree: Tree): Boolean = tree match { + case TypeApply(fun, targs) => + isClassTypeAccessible(fun) + case Select(module, apply) => + ( // SI-4859 `CaseClass1().InnerCaseClass2()` must not be rewritten to `new InnerCaseClass2()`; + // {expr; Outer}.Inner() must not be rewritten to `new Outer.Inner()`. + treeInfo.isQualifierSafeToElide(module) && + // SI-5626 Classes in refinement types cannot be constructed with `new`. In this case, + // the companion class is actually not a ClassSymbol, but a reference to an abstract type. + module.symbol.companionClass.isClass + ) + } + + val doTransform = + sym.isRealMethod && + sym.isCase && + sym.name == nme.apply && + isClassTypeAccessible(tree) + + if (doTransform) { + tree foreach { + case i@Ident(_) => + enterReference(i.pos, i.symbol) // SI-5390 need to `enterReference` for `a` in `a.B()` + case _ => + } + toConstructor(tree.pos, tree.tpe) + } + else { + ifNot + tree + } + } + + private def transformApply(tree: Apply): Tree = tree match { + case Apply( + Select(qual, nme.filter | nme.withFilter), + List(Function( + List(ValDef(_, pname, tpt, _)), + Match(_, CaseDef(pat1, _, _) :: _)))) + if ((pname startsWith nme.CHECK_IF_REFUTABLE_STRING) && + isIrrefutable(pat1, tpt.tpe) && (qual.tpe <:< tree.tpe)) => + + transform(qual) + + case Apply(fn, args) => + // sensicality should be subsumed by the unreachability/exhaustivity/irrefutability + // analyses in the pattern matcher + if (!inPattern) { + checkImplicitViewOptionApply(tree.pos, fn, args) + checkSensible(tree.pos, fn, args) + } + currentApplication = tree + tree + } + private def transformSelect(tree: Select): Tree = { + val Select(qual, _) = tree + val sym = tree.symbol + + checkUndesiredProperties(sym, tree.pos) + checkDelayedInitSelect(qual, sym, tree.pos) + + if (!sym.exists) + devWarning("Select node has NoSymbol! " + tree + " / " + tree.tpe) + else if (sym.isLocalToThis) + varianceValidator.checkForEscape(sym, currentClass) + + def checkSuper(mix: Name) = + // term should have been eliminated by super accessors + assert(!(qual.symbol.isTrait && sym.isTerm && mix == tpnme.EMPTY), (qual.symbol, sym, mix)) + + transformCaseApply(tree, + qual match { + case Super(_, mix) => checkSuper(mix) + case _ => + } + ) + } + private def transformIf(tree: If): Tree = { + val If(cond, thenpart, elsepart) = tree + def unitIfEmpty(t: Tree): Tree = + if (t == EmptyTree) Literal(Constant(())).setPos(tree.pos).setType(UnitTpe) else t + + cond.tpe match { + case ConstantType(value) => + val res = if (value.booleanValue) thenpart else elsepart + unitIfEmpty(res) + case _ => tree + } + } + + // Warning about nullary methods returning Unit. TODO: move to lint + private def checkNullaryMethodReturnType(sym: Symbol) = sym.tpe match { + case NullaryMethodType(restpe) if restpe.typeSymbol == UnitClass => + // this may be the implementation of e.g. a generic method being parameterized + // on Unit, in which case we had better let it slide. + val isOk = ( + sym.isGetter + || (sym.name containsName nme.DEFAULT_GETTER_STRING) + || sym.allOverriddenSymbols.exists(over => !(over.tpe.resultType =:= sym.tpe.resultType)) + ) + if (!isOk) + unit.warning(sym.pos, s"side-effecting nullary methods are discouraged: suggest defining as `def ${sym.name.decode}()` instead") + case _ => () + } + + /* Convert a reference to a case factory of type `tpe` to a new of the class it produces. */ + def toConstructor(pos: Position, tpe: Type)(implicit ctx: Context): Tree = { + val rtpe = tpe.finalResultType + assert(rtpe.typeSymbol.is(Case), tpe) + New(rtpe).withPos(pos).select(rtpe.typeSymbol.primaryConstructor) + } + private def isIrrefutable(pat: Tree, seltpe: Type): Boolean = pat match { + case Apply(_, args) => + val clazz = pat.tpe.typeSymbol + clazz == seltpe.typeSymbol && + clazz.isCaseClass && + (args corresponds clazz.primaryConstructor.tpe.asSeenFrom(seltpe, clazz).paramTypes)(isIrrefutable) + case Typed(pat, tpt) => + seltpe <:< tpt.tpe + case Ident(tpnme.WILDCARD) => + true + case Bind(_, pat) => + isIrrefutable(pat, seltpe) + case _ => + false + } + private def checkDelayedInitSelect(qual: Tree, sym: Symbol, pos: Position) = { + def isLikelyUninitialized = ( + (sym.owner isSubClass DelayedInitClass) + && !qual.tpe.isInstanceOf[ThisType] + && sym.accessedOrSelf.isVal + ) + if (settings.lint.value && isLikelyUninitialized) + unit.warning(pos, s"Selecting ${sym} from ${sym.owner}, which extends scala.DelayedInit, is likely to yield an uninitialized value") + } + private def lessAccessible(otherSym: Symbol, memberSym: Symbol): Boolean = ( + (otherSym != NoSymbol) + && !otherSym.isProtected + && !otherSym.isTypeParameterOrSkolem + && !otherSym.isExistentiallyBound + && (otherSym isLessAccessibleThan memberSym) + && (otherSym isLessAccessibleThan memberSym.enclClass) + ) + private def lessAccessibleSymsInType(other: Type, memberSym: Symbol): List[Symbol] = { + val extras = other match { + case TypeRef(pre, _, args) => + // checking the prefix here gives us spurious errors on e.g. a private[process] + // object which contains a type alias, which normalizes to a visible type. + args filterNot (_ eq NoPrefix) flatMap (tp => lessAccessibleSymsInType(tp, memberSym)) + case _ => + Nil + } + if (lessAccessible(other.typeSymbol, memberSym)) other.typeSymbol :: extras + else extras + } + private def warnLessAccessible(otherSym: Symbol, memberSym: Symbol) { + val comparison = accessFlagsToString(memberSym) match { + case "" => "" + case acc => " is " + acc + " but" + } + val cannot = + if (memberSym.isDeferred) "may be unable to provide a concrete implementation of" + else "may be unable to override" + + unit.warning(memberSym.pos, + "%s%s references %s %s.".format( + memberSym.fullLocationString, comparison, + accessFlagsToString(otherSym), otherSym + ) + "\nClasses which cannot access %s %s %s.".format( + otherSym.decodedName, cannot, memberSym.decodedName) + ) + } + + /** Warn about situations where a method signature will include a type which + * has more restrictive access than the method itself. + */ + private def checkAccessibilityOfReferencedTypes(tree: Tree) { + val member = tree.symbol + + def checkAccessibilityOfType(tpe: Type) { + val inaccessible = lessAccessibleSymsInType(tpe, member) + // if the unnormalized type is accessible, that's good enough + if (inaccessible.isEmpty) () + // or if the normalized type is, that's good too + else if ((tpe ne tpe.normalize) && lessAccessibleSymsInType(tpe.dealiasWiden, member).isEmpty) () + // otherwise warn about the inaccessible syms in the unnormalized type + else inaccessible foreach (sym => warnLessAccessible(sym, member)) + } + + // types of the value parameters + mapParamss(member)(p => checkAccessibilityOfType(p.tpe)) + // upper bounds of type parameters + member.typeParams.map(_.info.bounds.hi.widen) foreach checkAccessibilityOfType + } + + private def checkByNameRightAssociativeDef(tree: DefDef) { + tree match { + case DefDef(_, name, _, params :: _, _, _) => + if (settings.lint && !treeInfo.isLeftAssoc(name.decodedName) && params.exists(p => isByName(p.symbol))) + unit.warning(tree.pos, + "by-name parameters will be evaluated eagerly when called as a right-associative infix operator. For more details, see SI-1980.") + case _ => + } + } + override def transform(tree: Tree)(implicit ctx: Context): Tree = { + //val savedLocalTyper = localTyper + try { + val sym = tree.symbol + checkOverloadedRestrictions(ctx.owner) + checkAllOverrides(ctx.owner) + checkAnyValSubclass(ctx.owner) + if (ctx.owner.isDerivedValueClass) + ctx.owner.primaryConstructor.makeNotPrivateAfter(NoSymbol, thisTransformer) // SI-6601, must be done *after* pickler! + tree + + + // Apply RefChecks to annotations. Makes sure the annotations conform to + // type bounds (bug #935), issues deprecation warnings for symbols used + // inside annotations. + // applyRefchecksToAnnotations(tree) ??? + var result: Tree = tree match { + case tree: ValOrDefDef => + // move to lint: + // if (settings.warnNullaryUnit) + // checkNullaryMethodReturnType(sym) + // if (settings.warnInaccessible) { + // if (!sym.isConstructor && !sym.isEffectivelyFinal && !sym.isSynthetic) + // checkAccessibilityOfReferencedTypes(tree) + // } + // tree match { + // case dd: DefDef => checkByNameRightAssociativeDef(dd) + // case _ => + // } + tree + + case Template(constr, parents, self, body) => + // localTyper = localTyper.atOwner(tree, currentOwner) + checkOverloadedRestrictions(ctx.owner) + checkAllOverrides(ctx.owner) + checkAnyValSubclass(ctx.owner) + if (ctx.owner.isDerivedValueClass) + ctx.owner.primaryConstructor.makeNotPrivateAfter(NoSymbol, thisTransformer) // SI-6601, must be done *after* pickler! + tree + + case tpt: TypeTree => + transform(tpt.original) + tree + + case TypeApply(fn, args) => + checkBounds(tree, NoPrefix, NoSymbol, fn.tpe.typeParams, args map (_.tpe)) + transformCaseApply(tree, ()) + + case x @ Apply(_, _) => + transformApply(x) + + case x @ If(_, _, _) => + transformIf(x) + + case New(tpt) => + enterReference(tree.pos, tpt.tpe.typeSymbol) + tree + + case treeInfo.WildcardStarArg(_) if !isRepeatedParamArg(tree) => + unit.error(tree.pos, "no `: _*' annotation allowed here\n" + + "(such annotations are only allowed in arguments to *-parameters)") + tree + + case Ident(name) => + checkUndesiredProperties(sym, tree.pos) + transformCaseApply(tree, + if (name != nme.WILDCARD && name != tpnme.WILDCARD_STAR) { + assert(sym != NoSymbol, "transformCaseApply: name = " + name.debugString + " tree = " + tree + " / " + tree.getClass) //debug + enterReference(tree.pos, sym) + } + ) + + case x @ Select(_, _) => + transformSelect(x) + + case UnApply(fun, args) => + transform(fun) // just make sure we enterReference for unapply symbols, note that super.transform(tree) would not transform(fun) + // transformTrees(args) // TODO: is this necessary? could there be forward references in the args?? + // probably not, until we allow parameterised extractors + tree + + + case _ => tree + } + + // skip refchecks in patterns.... + result = result match { + case CaseDef(pat, guard, body) => + val pat1 = savingInPattern { + inPattern = true + transform(pat) + } + treeCopy.CaseDef(tree, pat1, transform(guard), transform(body)) + case LabelDef(_, _, _) if treeInfo.hasSynthCaseSymbol(result) => + savingInPattern { + inPattern = true + deriveLabelDef(result)(transform) + } + case Apply(fun, args) if fun.symbol.isLabel && treeInfo.isSynthCaseSymbol(fun.symbol) => + savingInPattern { + // SI-7756 If we were in a translated pattern, we can now switch out of pattern mode, as the label apply signals + // that we are in the user-supplied code in the case body. + // + // Relies on the translation of: + // (null: Any) match { case x: List[_] => x; x.reverse; case _ => }' + // to: + // <synthetic> val x2: List[_] = (x1.asInstanceOf[List[_]]: List[_]); + // matchEnd4({ x2; x2.reverse}) // case body is an argument to a label apply. + inPattern = false + super.transform(result) + } + case ValDef(_, _, _, _) if treeInfo.hasSynthCaseSymbol(result) => + deriveValDef(result)(transform) // SI-7716 Don't refcheck the tpt of the synthetic val that holds the selector. + case _ => + super.transform(result) + } + result match { + case ClassDef(_, _, _, _) + | TypeDef(_, _, _, _) => + if (result.symbol.isLocalToBlock || result.symbol.isTopLevel) + varianceValidator.traverse(result) + case tt @ TypeTree() if tt.original != null => + varianceValidator.traverse(tt.original) // See SI-7872 + case _ => + } + + checkUnexpandedMacro(result) + + result + } catch { + case ex: TypeError => + if (settings.debug) ex.printStackTrace() + unit.error(tree.pos, ex.getMessage()) + tree + } finally { + localTyper = savedLocalTyper + currentApplication = savedCurrentApplication + } + } +*/ + diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala new file mode 100644 index 000000000..ee2d68278 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -0,0 +1,524 @@ +package dotty.tools +package dotc +package typer + +import core._ +import ast._ +import Scopes._, Contexts._, Constants._, Types._, Symbols._, Names._, Flags._, Decorators._ +import ErrorReporting._, Annotations._, Denotations._, SymDenotations._, StdNames._, TypeErasure._ +import TypeApplications.AppliedType +import util.Positions._ +import config.Printers.typr +import ast.Trees._ +import NameOps._ +import collection.mutable +import reporting.diagnostic.Message +import reporting.diagnostic.messages._ + +trait TypeAssigner { + import tpd._ + + /** The qualifying class of a this or super with prefix `qual` (which might be empty). + * @param packageOk The qualifier may refer to a package. + */ + def qualifyingClass(tree: untpd.Tree, qual: Name, packageOK: Boolean)(implicit ctx: Context): Symbol = { + def qualifies(sym: Symbol) = + sym.isClass && ( + qual.isEmpty || + sym.name == qual || + sym.is(Module) && sym.name.stripModuleClassSuffix == qual) + ctx.outersIterator.map(_.owner).find(qualifies) match { + case Some(c) if packageOK || !(c is Package) => + c + case _ => + ctx.error( + if (qual.isEmpty) tree.show + " can be used only in a class, object, or template" + else qual.show + " is not an enclosing class", tree.pos) + NoSymbol + } + } + + /** An upper approximation of the given type `tp` that does not refer to any symbol in `symsToAvoid`. + * Approximation steps are: + * + * - follow aliases and upper bounds if the original refers to a forbidden symbol + * - widen termrefs that refer to a forbidden symbol + * - replace ClassInfos of forbidden classes by the intersection of their parents, refined by all + * non-private fields, methods, and type members. + * - if the prefix of a class refers to a forbidden symbol, first try to replace the prefix, + * if this is not possible, replace the ClassInfo as above. + * - drop refinements referring to a forbidden symbol. + */ + def avoid(tp: Type, symsToAvoid: => List[Symbol])(implicit ctx: Context): Type = { + val widenMap = new TypeMap { + lazy val forbidden = symsToAvoid.toSet + def toAvoid(tp: Type): Boolean = + // TODO: measure the cost of using `existsPart`, and if necessary replace it + // by a `TypeAccumulator` where we have set `stopAtStatic = true`. + tp existsPart { + case tp: NamedType => forbidden contains tp.symbol + case tp: ThisType => forbidden contains tp.cls + case _ => false + } + def apply(tp: Type): Type = tp match { + case tp: TermRef + if toAvoid(tp) && (variance > 0 || tp.info.widenExpr <:< tp) => + // Can happen if `x: y.type`, then `x.type =:= y.type`, hence we can widen `x.type` + // to y.type in all contexts, not just covariant ones. + apply(tp.info.widenExpr) + case tp: TypeRef if toAvoid(tp) => + tp.info match { + case TypeAlias(ref) => + apply(ref) + case info: ClassInfo if variance > 0 => + if (!(forbidden contains tp.symbol)) { + val prefix = apply(tp.prefix) + val tp1 = tp.derivedSelect(prefix) + if (tp1.typeSymbol.exists) + return tp1 + } + val parentType = info.parentsWithArgs.reduceLeft(ctx.typeComparer.andType(_, _)) + def addRefinement(parent: Type, decl: Symbol) = { + val inherited = + parentType.findMember(decl.name, info.cls.thisType, Private) + .suchThat(decl.matches(_)) + val inheritedInfo = inherited.info + if (inheritedInfo.exists && decl.info <:< inheritedInfo && !(inheritedInfo <:< decl.info)) { + val r = RefinedType(parent, decl.name, decl.info) + typr.println(i"add ref $parent $decl --> " + r) + r + } + else + parent + } + val refinableDecls = info.decls.filterNot( + sym => sym.is(TypeParamAccessor | Private) || sym.isConstructor) + val fullType = (parentType /: refinableDecls)(addRefinement) + mapOver(fullType) + case TypeBounds(lo, hi) if variance > 0 => + apply(hi) + case _ => + mapOver(tp) + } + case tp @ HKApply(tycon, args) if toAvoid(tycon) => + apply(tp.superType) + case tp @ AppliedType(tycon, args) if toAvoid(tycon) => + val base = apply(tycon) + var args = tp.baseArgInfos(base.typeSymbol) + if (base.typeParams.length != args.length) + args = base.typeParams.map(_.paramBounds) + apply(base.appliedTo(args)) + case tp @ RefinedType(parent, name, rinfo) if variance > 0 => + val parent1 = apply(tp.parent) + val refinedInfo1 = apply(rinfo) + if (toAvoid(refinedInfo1)) { + typr.println(s"dropping refinement from $tp") + if (name.isTypeName) tp.derivedRefinedType(parent1, name, TypeBounds.empty) + else parent1 + } else { + tp.derivedRefinedType(parent1, name, refinedInfo1) + } + case tp: TypeVar if ctx.typerState.constraint.contains(tp) => + val lo = ctx.typerState.constraint.fullLowerBound(tp.origin) + val lo1 = avoid(lo, symsToAvoid) + if (lo1 ne lo) lo1 else tp + case _ => + mapOver(tp) + } + } + widenMap(tp) + } + + def avoidingType(expr: Tree, bindings: List[Tree])(implicit ctx: Context): Type = + avoid(expr.tpe, localSyms(bindings).filter(_.isTerm)) + + def seqToRepeated(tree: Tree)(implicit ctx: Context): Tree = + Typed(tree, TypeTree(tree.tpe.widen.translateParameterized(defn.SeqClass, defn.RepeatedParamClass))) + + /** A denotation exists really if it exists and does not point to a stale symbol. */ + final def reallyExists(denot: Denotation)(implicit ctx: Context): Boolean = try + denot match { + case denot: SymDenotation => + denot.exists && { + denot.ensureCompleted + !denot.isAbsent + } + case denot: SingleDenotation => + val sym = denot.symbol + (sym eq NoSymbol) || reallyExists(sym.denot) + case _ => + true + } + catch { + case ex: StaleSymbol => false + } + + /** If `tpe` is a named type, check that its denotation is accessible in the + * current context. Return the type with those alternatives as denotations + * which are accessible. + * + * Also performs the following normalizations on the type `tpe`. + * (1) parameter accessors are always dereferenced. + * (2) if the owner of the denotation is a package object, it is assured + * that the package object shows up as the prefix. + */ + def ensureAccessible(tpe: Type, superAccess: Boolean, pos: Position)(implicit ctx: Context): Type = { + def test(tpe: Type, firstTry: Boolean): Type = tpe match { + case tpe: NamedType => + val pre = tpe.prefix + val name = tpe.name + val d = tpe.denot.accessibleFrom(pre, superAccess) + if (!d.exists) { + // it could be that we found an inaccessible private member, but there is + // an inherited non-private member with the same name and signature. + val d2 = pre.nonPrivateMember(name) + if (reallyExists(d2) && firstTry) + test(tpe.shadowed.withDenot(d2), false) + else if (pre.derivesFrom(defn.DynamicClass)) { + TryDynamicCallType + } else { + val alts = tpe.denot.alternatives.map(_.symbol).filter(_.exists) + val what = alts match { + case Nil => + name.toString + case sym :: Nil => + if (sym.owner == pre.typeSymbol) sym.show else sym.showLocated + case _ => + em"none of the overloaded alternatives named $name" + } + val where = if (ctx.owner.exists) s" from ${ctx.owner.enclosingClass}" else "" + val whyNot = new StringBuffer + alts foreach (_.isAccessibleFrom(pre, superAccess, whyNot)) + if (!tpe.isError) + ctx.error(ex"$what cannot be accessed as a member of $pre$where.$whyNot", pos) + ErrorType + } + } + else if (d.symbol is TypeParamAccessor) + if (d.info.isAlias) + ensureAccessible(d.info.bounds.hi, superAccess, pos) + else // It's a named parameter, use the non-symbolic representation to pick up inherited versions as well + d.symbol.owner.thisType.select(d.symbol.name) + else + ctx.makePackageObjPrefixExplicit(tpe withDenot d) + case _ => + tpe + } + test(tpe, true) + } + + /** The type of a selection with `name` of a tree with type `site`. + */ + def selectionType(site: Type, name: Name, pos: Position)(implicit ctx: Context): Type = { + val mbr = site.member(name) + if (reallyExists(mbr)) site.select(name, mbr) + else if (site.derivesFrom(defn.DynamicClass) && !Dynamic.isDynamicMethod(name)) { + TryDynamicCallType + } else { + if (!site.isErroneous) { + def kind = if (name.isTypeName) "type" else "value" + def addendum = + if (site.derivesFrom(defn.DynamicClass)) "\npossible cause: maybe a wrong Dynamic method signature?" + else "" + ctx.error( + if (name == nme.CONSTRUCTOR) ex"$site does not have a constructor" + else NotAMember(site, name, kind), + pos) + } + ErrorType + } + } + + /** The selection type, which is additionally checked for accessibility. + */ + def accessibleSelectionType(tree: untpd.RefTree, qual1: Tree)(implicit ctx: Context): Type = { + val ownType = selectionType(qual1.tpe.widenIfUnstable, tree.name, tree.pos) + ensureAccessible(ownType, qual1.isInstanceOf[Super], tree.pos) + } + + /** Type assignment method. Each method takes as parameters + * - an untpd.Tree to which it assigns a type, + * - typed child trees it needs to access to cpmpute that type, + * - any further information it needs to access to compute that type. + */ + + def assignType(tree: untpd.Ident, tp: Type)(implicit ctx: Context) = + tree.withType(tp) + + def assignType(tree: untpd.Select, qual: Tree)(implicit ctx: Context): Select = { + def qualType = qual.tpe.widen + def arrayElemType = { + val JavaArrayType(elemtp) = qualType + elemtp + } + val p = nme.primitive + val tp = tree.name match { + case p.arrayApply => MethodType(defn.IntType :: Nil, arrayElemType) + case p.arrayUpdate => MethodType(defn.IntType :: arrayElemType :: Nil, defn.UnitType) + case p.arrayLength => MethodType(Nil, defn.IntType) + + // Note that we do not need to handle calls to Array[T]#clone() specially: + // The JLS section 10.7 says "The return type of the clone method of an array type + // T[] is T[]", but the actual return type at the bytecode level is Object which + // is casted to T[] by javac. Since the return type of Array[T]#clone() is Array[T], + // this is exactly what Erasure will do. + + case _ => accessibleSelectionType(tree, qual) + } + tree.withType(tp) + } + + def assignType(tree: untpd.New, tpt: Tree)(implicit ctx: Context) = + tree.withType(tpt.tpe) + + def assignType(tree: untpd.Literal)(implicit ctx: Context) = + tree.withType { + val value = tree.const + value.tag match { + case UnitTag => defn.UnitType + case NullTag => defn.NullType + case _ => if (ctx.erasedTypes) value.tpe else ConstantType(value) + } + } + + def assignType(tree: untpd.This)(implicit ctx: Context) = { + val cls = qualifyingClass(tree, tree.qual.name, packageOK = false) + tree.withType(cls.thisType) + } + + def assignType(tree: untpd.Super, qual: Tree, inConstrCall: Boolean, mixinClass: Symbol = NoSymbol)(implicit ctx: Context) = { + val mix = tree.mix + val qtype @ ThisType(_) = qual.tpe + val cls = qtype.cls + + def findMixinSuper(site: Type): Type = site.parents filter (_.name == mix.name) match { + case p :: Nil => + p + case Nil => + errorType(em"$mix does not name a parent class of $cls", tree.pos) + case p :: q :: _ => + errorType("ambiguous parent class qualifier", tree.pos) + } + val owntype = + if (mixinClass.exists) mixinClass.typeRef + else if (!mix.isEmpty) findMixinSuper(cls.info) + else if (inConstrCall || ctx.erasedTypes) cls.info.firstParent + else { + val ps = cls.classInfo.parentsWithArgs + if (ps.isEmpty) defn.AnyType else ps.reduceLeft((x: Type, y: Type) => x & y) + } + tree.withType(SuperType(cls.thisType, owntype)) + } + + def assignType(tree: untpd.Apply, fn: Tree, args: List[Tree])(implicit ctx: Context) = { + val ownType = fn.tpe.widen match { + case fntpe @ MethodType(_, ptypes) => + if (sameLength(ptypes, args) || ctx.phase.prev.relaxedTyping) fntpe.instantiate(args.tpes) + else wrongNumberOfArgs(fn.tpe, "", fntpe.typeParams, args, tree.pos) + case t => + errorType(i"${err.exprStr(fn)} does not take parameters", tree.pos) + } + tree.withType(ownType) + } + + def assignType(tree: untpd.TypeApply, fn: Tree, args: List[Tree])(implicit ctx: Context) = { + val ownType = fn.tpe.widen match { + case pt: PolyType => + val paramNames = pt.paramNames + if (hasNamedArg(args)) { + // Type arguments which are specified by name (immutable after this first loop) + val namedArgMap = new mutable.HashMap[Name, Type] + for (NamedArg(name, arg) <- args) + if (namedArgMap.contains(name)) + ctx.error("duplicate name", arg.pos) + else if (!paramNames.contains(name)) + ctx.error(s"undefined parameter name, required: ${paramNames.mkString(" or ")}", arg.pos) + else + namedArgMap(name) = arg.tpe + + // Holds indexes of non-named typed arguments in paramNames + val gapBuf = new mutable.ListBuffer[Int] + def nextPoly(idx: Int) = { + val newIndex = gapBuf.length + gapBuf += idx + // Re-index unassigned type arguments that remain after transformation + PolyParam(pt, newIndex) + } + + // Type parameters after naming assignment, conserving paramNames order + val normArgs: List[Type] = paramNames.zipWithIndex.map { case (pname, idx) => + namedArgMap.getOrElse(pname, nextPoly(idx)) + } + + val transform = new TypeMap { + def apply(t: Type) = t match { + case PolyParam(`pt`, idx) => normArgs(idx) + case _ => mapOver(t) + } + } + val resultType1 = transform(pt.resultType) + if (gapBuf.isEmpty) resultType1 + else { + val gaps = gapBuf.toList + pt.derivedPolyType( + gaps.map(paramNames), + gaps.map(idx => transform(pt.paramBounds(idx)).bounds), + resultType1) + } + } + else { + val argTypes = args.tpes + if (sameLength(argTypes, paramNames) || ctx.phase.prev.relaxedTyping) pt.instantiate(argTypes) + else wrongNumberOfArgs(fn.tpe, "type", pt.typeParams, args, tree.pos) + } + case _ => + errorType(i"${err.exprStr(fn)} does not take type parameters", tree.pos) + } + + tree.withType(ownType) + } + + def assignType(tree: untpd.Typed, tpt: Tree)(implicit ctx: Context) = + tree.withType(tpt.tpe) + + def assignType(tree: untpd.NamedArg, arg: Tree)(implicit ctx: Context) = + tree.withType(arg.tpe) + + def assignType(tree: untpd.Assign)(implicit ctx: Context) = + tree.withType(defn.UnitType) + + def assignType(tree: untpd.Block, stats: List[Tree], expr: Tree)(implicit ctx: Context) = + tree.withType(avoidingType(expr, stats)) + + def assignType(tree: untpd.Inlined, bindings: List[Tree], expansion: Tree)(implicit ctx: Context) = + tree.withType(avoidingType(expansion, bindings)) + + def assignType(tree: untpd.If, thenp: Tree, elsep: Tree)(implicit ctx: Context) = + tree.withType(thenp.tpe | elsep.tpe) + + def assignType(tree: untpd.Closure, meth: Tree, target: Tree)(implicit ctx: Context) = + tree.withType( + if (target.isEmpty) meth.tpe.widen.toFunctionType(tree.env.length) + else target.tpe) + + def assignType(tree: untpd.CaseDef, body: Tree)(implicit ctx: Context) = + tree.withType(body.tpe) + + def assignType(tree: untpd.Match, cases: List[CaseDef])(implicit ctx: Context) = + tree.withType(ctx.typeComparer.lub(cases.tpes)) + + def assignType(tree: untpd.Return)(implicit ctx: Context) = + tree.withType(defn.NothingType) + + def assignType(tree: untpd.Try, expr: Tree, cases: List[CaseDef])(implicit ctx: Context) = + 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], elemtpt: Tree)(implicit ctx: Context) = { + val ownType = tree match { + case tree: untpd.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) = + tree.withType(ref.tpe) + + def assignType(tree: untpd.AndTypeTree, left: Tree, right: Tree)(implicit ctx: Context) = + tree.withType(left.tpe & right.tpe) + + def assignType(tree: untpd.OrTypeTree, left: Tree, right: Tree)(implicit ctx: Context) = + tree.withType(left.tpe | right.tpe) + + /** Assign type of RefinedType. + * Refinements are typed as if they were members of refinement class `refineCls`. + */ + def assignType(tree: untpd.RefinedTypeTree, parent: Tree, refinements: List[Tree], refineCls: ClassSymbol)(implicit ctx: Context) = { + def addRefinement(parent: Type, refinement: Tree): Type = { + val rsym = refinement.symbol + val rinfo = if (rsym is Accessor) rsym.info.resultType else rsym.info + RefinedType(parent, rsym.name, rinfo) + } + val refined = (parent.tpe /: refinements)(addRefinement) + tree.withType(RecType.closeOver(rt => refined.substThis(refineCls, RecThis(rt)))) + } + + def assignType(tree: untpd.AppliedTypeTree, tycon: Tree, args: List[Tree])(implicit ctx: Context) = { + val tparams = tycon.tpe.typeParams + lazy val ntparams = tycon.tpe.namedTypeParams + def refineNamed(tycon: Type, arg: Tree) = arg match { + case ast.Trees.NamedArg(name, argtpt) => + // Dotty deviation: importing ast.Trees._ and matching on NamedArg gives a cyclic ref error + val tparam = tparams.find(_.paramName == name) match { + case Some(tparam) => tparam + case none => ntparams.find(_.name == name).getOrElse(NoSymbol) + } + if (tparam.isTypeParam) RefinedType(tycon, name, argtpt.tpe.toBounds(tparam)) + else errorType(i"$tycon does not have a parameter or abstract type member named $name", arg.pos) + case _ => + errorType(s"named and positional type arguments may not be mixed", arg.pos) + } + val ownType = + if (hasNamedArg(args)) (tycon.tpe /: args)(refineNamed) + else if (sameLength(tparams, args)) tycon.tpe.appliedTo(args.tpes) + else wrongNumberOfArgs(tycon.tpe, "type", tparams, args, tree.pos) + tree.withType(ownType) + } + + def assignType(tree: untpd.PolyTypeTree, tparamDefs: List[TypeDef], body: Tree)(implicit ctx: Context) = + tree.withType(body.tpe.LambdaAbstract(tparamDefs.map(_.symbol))) + + def assignType(tree: untpd.ByNameTypeTree, result: Tree)(implicit ctx: Context) = + tree.withType(ExprType(result.tpe)) + + def assignType(tree: untpd.TypeBoundsTree, lo: Tree, hi: Tree)(implicit ctx: Context) = + tree.withType(if (lo eq hi) TypeAlias(lo.tpe) else TypeBounds(lo.tpe, hi.tpe)) + + def assignType(tree: untpd.Bind, sym: Symbol)(implicit ctx: Context) = + tree.withType(NamedType.withFixedSym(NoPrefix, sym)) + + def assignType(tree: untpd.Alternative, trees: List[Tree])(implicit ctx: Context) = + tree.withType(ctx.typeComparer.lub(trees.tpes)) + + def assignType(tree: untpd.UnApply, proto: Type)(implicit ctx: Context) = + tree.withType(proto) + + def assignType(tree: untpd.ValDef, sym: Symbol)(implicit ctx: Context) = + tree.withType(if (sym.exists) assertExists(symbolicIfNeeded(sym).orElse(sym.valRef)) else NoType) + + def assignType(tree: untpd.DefDef, sym: Symbol)(implicit ctx: Context) = + tree.withType(symbolicIfNeeded(sym).orElse(sym.termRefWithSig)) + + def assignType(tree: untpd.TypeDef, sym: Symbol)(implicit ctx: Context) = + tree.withType(symbolicIfNeeded(sym).orElse(sym.typeRef)) + + private def symbolicIfNeeded(sym: Symbol)(implicit ctx: Context) = { + val owner = sym.owner + owner.infoOrCompleter match { + case info: ClassInfo if info.givenSelfType.exists => + // In that case a simple typeRef/termWithWithSig could return a member of + // the self type, not the symbol itself. To avoid this, we make the reference + // symbolic. In general it seems to be faster to keep the non-symblic + // reference, since there is less pressure on the uniqueness tables that way + // and less work to update all the different references. That's why symbolic references + // are only used if necessary. + NamedType.withFixedSym(owner.thisType, sym) + case _ => NoType + } + } + + def assertExists(tp: Type) = { assert(tp != NoType); tp } + + def assignType(tree: untpd.Import, sym: Symbol)(implicit ctx: Context) = + tree.withType(sym.nonMemberTermRef) + + def assignType(tree: untpd.Annotated, arg: Tree, annot: Tree)(implicit ctx: Context) = + tree.withType(AnnotatedType(arg.tpe.widen, Annotation(annot))) + + def assignType(tree: untpd.PackageDef, pid: Tree)(implicit ctx: Context) = + tree.withType(pid.symbol.valRef) +} + +object TypeAssigner extends TypeAssigner + diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala new file mode 100644 index 000000000..64936e106 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -0,0 +1,1952 @@ +package dotty.tools +package dotc +package typer + +import core._ +import ast._ +import Trees._ +import Constants._ +import StdNames._ +import Scopes._ +import Denotations._ +import ProtoTypes._ +import Contexts._ +import Comments._ +import Symbols._ +import Types._ +import SymDenotations._ +import Annotations._ +import Names._ +import NameOps._ +import Flags._ +import Decorators._ +import ErrorReporting._ +import Checking._ +import Inferencing._ +import EtaExpansion.etaExpand +import dotty.tools.dotc.transform.Erasure.Boxing +import util.Positions._ +import util.common._ +import util.SourcePosition +import collection.mutable +import annotation.tailrec +import Implicits._ +import util.Stats.{track, record} +import config.Printers.{typr, gadts} +import rewrite.Rewrites.patch +import NavigateAST._ +import transform.SymUtils._ +import language.implicitConversions +import printing.SyntaxHighlighting._ + +object Typer { + + /** The precedence of bindings which determines which of several bindings will be + * accessed by an Ident. + */ + object BindingPrec { + val definition = 4 + val namedImport = 3 + val wildImport = 2 + val packageClause = 1 + val nothingBound = 0 + def isImportPrec(prec: Int) = prec == namedImport || prec == wildImport + } + + /** Assert tree has a position, unless it is empty or a typed splice */ + def assertPositioned(tree: untpd.Tree)(implicit ctx: Context) = + if (!tree.isEmpty && !tree.isInstanceOf[untpd.TypedSplice] && ctx.typerState.isGlobalCommittable) + assert(tree.pos.exists, s"position not set for $tree # ${tree.uniqueId}") +} + +class Typer extends Namer with TypeAssigner with Applications with Implicits with Dynamic with Checking with Docstrings { + + import Typer._ + import tpd.{cpy => _, _} + import untpd.cpy + import Dynamic.isDynamicMethod + import reporting.diagnostic.Message + import reporting.diagnostic.messages._ + + /** A temporary data item valid for a single typed ident: + * The set of all root import symbols that have been + * encountered as a qualifier of an import so far. + * Note: It would be more proper to move importedFromRoot into typedIdent. + * We should check that this has no performance degradation, however. + */ + private var importedFromRoot: Set[Symbol] = Set() + + /** Temporary data item for single call to typed ident: + * This symbol would be found under Scala2 mode, but is not + * in dotty (because dotty conforms to spec section 2 + * wrt to package member resolution but scalac doe not). + */ + private var foundUnderScala2: Type = NoType + + def newLikeThis: Typer = new Typer + + /** Attribute an identifier consisting of a simple name or wildcard + * + * @param tree The tree representing the identifier. + * Transformations: (1) Prefix class members with this. + * (2) Change imported symbols to selections. + * (3) Change pattern Idents id (but not wildcards) to id @ _ + */ + def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context): Tree = track("typedIdent") { + val refctx = ctx + val name = tree.name + val noImports = ctx.mode.is(Mode.InPackageClauseName) + + /** Method is necessary because error messages need to bind to + * to typedIdent's context which is lost in nested calls to findRef + */ + def error(msg: => Message, pos: Position) = ctx.error(msg, pos) + + /** Is this import a root import that has been shadowed by an explicit + * import in the same program? + */ + def isDisabled(imp: ImportInfo, site: Type): Boolean = { + if (imp.isRootImport && (importedFromRoot contains site.termSymbol)) return true + if (imp.hiddenRoot.exists) importedFromRoot += imp.hiddenRoot + false + } + + /** Does this identifier appear as a constructor of a pattern? */ + def isPatternConstr = + if (ctx.mode.isExpr && (ctx.outer.mode is Mode.Pattern)) + ctx.outer.tree match { + case Apply(`tree`, _) => true + case _ => false + } + else false + + /** A symbol qualifies if it really exists. In addition, + * if we are in a constructor of a pattern, we ignore all definitions + * which are methods and not accessors (note: if we don't do that + * case x :: xs in class List would return the :: method). + */ + def qualifies(denot: Denotation): Boolean = + reallyExists(denot) && !( + pt.isInstanceOf[UnapplySelectionProto] && + (denot.symbol is (Method, butNot = Accessor))) + + /** Find the denotation of enclosing `name` in given context `ctx`. + * @param previous A denotation that was found in a more deeply nested scope, + * or else `NoDenotation` if nothing was found yet. + * @param prevPrec The binding precedence of the previous denotation, + * or else `nothingBound` if nothing was found yet. + * @param prevCtx The context of the previous denotation, + * or else `NoContext` if nothing was found yet. + */ + def findRef(previous: Type, prevPrec: Int, prevCtx: Context)(implicit ctx: Context): Type = { + import BindingPrec._ + + /** A string which explains how something was bound; Depending on `prec` this is either + * imported by <tree> + * or defined in <symbol> + */ + def bindingString(prec: Int, whereFound: Context, qualifier: String = "") = + if (prec == wildImport || prec == namedImport) { + ex"""imported$qualifier by ${hl"${whereFound.importInfo.toString}"}""" + } else + ex"""defined$qualifier in ${hl"${whereFound.owner.toString}"}""" + + /** Check that any previously found result from an inner context + * does properly shadow the new one from an outer context. + * @param found The newly found result + * @param newPrec Its precedence + * @param scala2pkg Special mode where we check members of the same package, but defined + * in different compilation units under Scala2. If set, and the + * previous and new contexts do not have the same scope, we select + * the previous (inner) definition. This models what scalac does. + */ + def checkNewOrShadowed(found: Type, newPrec: Int, scala2pkg: Boolean = false)(implicit ctx: Context): Type = + if (!previous.exists || ctx.typeComparer.isSameRef(previous, found)) found + else if ((prevCtx.scope eq ctx.scope) && + (newPrec == definition || + newPrec == namedImport && prevPrec == wildImport)) { + // special cases: definitions beat imports, and named imports beat + // wildcard imports, provided both are in contexts with same scope + found + } + else { + if (!scala2pkg && !previous.isError && !found.isError) { + error( + ex"""|reference to `$name` is ambiguous + |it is both ${bindingString(newPrec, ctx, "")} + |and ${bindingString(prevPrec, prevCtx, " subsequently")}""", + tree.pos) + } + previous + } + + /** The type representing a named import with enclosing name when imported + * from given `site` and `selectors`. + */ + def namedImportRef(site: Type, selectors: List[untpd.Tree])(implicit ctx: Context): Type = { + def checkUnambiguous(found: Type) = { + val other = namedImportRef(site, selectors.tail) + if (other.exists && found.exists && (found != other)) + error(em"reference to `$name` is ambiguous; it is imported twice in ${ctx.tree}", + tree.pos) + found + } + val Name = name.toTermName.decode + selectors match { + case selector :: rest => + selector match { + case Thicket(fromId :: Ident(Name) :: _) => + val Ident(from) = fromId + val selName = if (name.isTypeName) from.toTypeName else from + // Pass refctx so that any errors are reported in the context of the + // reference instead of the context of the import. + checkUnambiguous(selectionType(site, selName, tree.pos)(refctx)) + case Ident(Name) => + checkUnambiguous(selectionType(site, name, tree.pos)(refctx)) + case _ => + namedImportRef(site, rest) + } + case nil => + NoType + } + } + + /** The type representing a wildcard import with enclosing name when imported + * from given import info + */ + def wildImportRef(imp: ImportInfo)(implicit ctx: Context): Type = { + if (imp.isWildcardImport) { + val pre = imp.site + if (!isDisabled(imp, pre) && !(imp.excluded contains name.toTermName) && name != nme.CONSTRUCTOR) { + val denot = pre.member(name).accessibleFrom(pre)(refctx) + if (reallyExists(denot)) return pre.select(name, denot) + } + } + NoType + } + + /** Is (some alternative of) the given predenotation `denot` + * defined in current compilation unit? + */ + def isDefinedInCurrentUnit(denot: Denotation)(implicit ctx: Context): Boolean = denot match { + case MultiDenotation(d1, d2) => isDefinedInCurrentUnit(d1) || isDefinedInCurrentUnit(d2) + case denot: SingleDenotation => denot.symbol.sourceFile == ctx.source.file + } + + /** Is `denot` the denotation of a self symbol? */ + def isSelfDenot(denot: Denotation)(implicit ctx: Context) = denot match { + case denot: SymDenotation => denot is SelfName + case _ => false + } + + /** Would import of kind `prec` be not shadowed by a nested higher-precedence definition? */ + def isPossibleImport(prec: Int)(implicit ctx: Context) = + !noImports && + (prevPrec < prec || prevPrec == prec && (prevCtx.scope eq ctx.scope)) + + @tailrec def loop(implicit ctx: Context): Type = { + if (ctx.scope == null) previous + else { + val outer = ctx.outer + var result: Type = NoType + + // find definition + if ((ctx.scope ne outer.scope) || (ctx.owner ne outer.owner)) { + val defDenot = ctx.denotNamed(name) + if (qualifies(defDenot)) { + val curOwner = ctx.owner + val found = + if (isSelfDenot(defDenot)) curOwner.enclosingClass.thisType + else curOwner.thisType.select(name, defDenot) + if (!(curOwner is Package) || isDefinedInCurrentUnit(defDenot)) + result = checkNewOrShadowed(found, definition) // no need to go further out, we found highest prec entry + else { + if (ctx.scala2Mode && !foundUnderScala2.exists) + foundUnderScala2 = checkNewOrShadowed(found, definition, scala2pkg = true) + if (defDenot.symbol is Package) + result = checkNewOrShadowed(previous orElse found, packageClause) + else if (prevPrec < packageClause) + result = findRef(found, packageClause, ctx)(outer) + } + } + } + + if (result.exists) result + else { // find import + val curImport = ctx.importInfo + if (ctx.owner.is(Package) && curImport != null && curImport.isRootImport && previous.exists) + previous // no more conflicts possible in this case + else if (isPossibleImport(namedImport) && (curImport ne outer.importInfo) && !curImport.sym.isCompleting) { + val namedImp = namedImportRef(curImport.site, curImport.selectors) + if (namedImp.exists) + findRef(checkNewOrShadowed(namedImp, namedImport), namedImport, ctx)(outer) + else if (isPossibleImport(wildImport)) { + val wildImp = wildImportRef(curImport) + if (wildImp.exists) + findRef(checkNewOrShadowed(wildImp, wildImport), wildImport, ctx)(outer) + else loop(outer) + } + else loop(outer) + } + else loop(outer) + } + } + } + + loop + } + + // begin typedIdent + def kind = if (name.isTermName) "" else "type " + typr.println(s"typed ident $kind$name in ${ctx.owner}") + if (ctx.mode is Mode.Pattern) { + if (name == nme.WILDCARD) + return tree.withType(pt) + if (isVarPattern(tree) && name.isTermName) + return typed(desugar.patternVar(tree), pt) + } + + + val rawType = { + val saved1 = importedFromRoot + val saved2 = foundUnderScala2 + importedFromRoot = Set.empty + foundUnderScala2 = NoType + try { + var found = findRef(NoType, BindingPrec.nothingBound, NoContext) + if (foundUnderScala2.exists && !(foundUnderScala2 =:= found)) { + ctx.migrationWarning( + ex"""Name resolution will change. + | currently selected : $foundUnderScala2 + | in the future, without -language:Scala2: $found""", tree.pos) + found = foundUnderScala2 + } + found + } + finally { + importedFromRoot = saved1 + foundUnderScala2 = saved2 + } + } + + val ownType = + if (rawType.exists) + ensureAccessible(rawType, superAccess = false, tree.pos) + else { + error(new MissingIdent(tree, kind, name.show), tree.pos) + ErrorType + } + + val tree1 = ownType match { + case ownType: NamedType if !prefixIsElidable(ownType) => + ref(ownType).withPos(tree.pos) + case _ => + tree.withType(ownType) + } + + checkValue(tree1, pt) + } + + private def typedSelect(tree: untpd.Select, pt: Type, qual: Tree)(implicit ctx: Context): Select = + healNonvariant( + checkValue(assignType(cpy.Select(tree)(qual, tree.name), qual), pt), + pt) + + /** Let `tree = p.n` where `p: T`. If tree's type is an unsafe instantiation + * (see TypeOps#asSeenFrom for how this can happen), rewrite the prefix `p` + * to `(p: <unknown skolem of type T>)` and try again with the new (stable) + * prefix. If the result has another unsafe instantiation, raise an error. + */ + private def healNonvariant[T <: Tree](tree: T, pt: Type)(implicit ctx: Context): T = + if (ctx.unsafeNonvariant == ctx.runId && tree.tpe.widen.hasUnsafeNonvariant) + tree match { + case tree @ Select(qual, _) if !qual.tpe.isStable => + val alt = typedSelect(tree, pt, Typed(qual, TypeTree(SkolemType(qual.tpe.widen)))) + typr.println(i"healed type: ${tree.tpe} --> $alt") + alt.asInstanceOf[T] + case _ => + ctx.error(ex"unsafe instantiation of type ${tree.tpe}", tree.pos) + tree + } + else tree + + def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = track("typedSelect") { + def typeSelectOnTerm(implicit ctx: Context): Tree = { + val qual1 = typedExpr(tree.qualifier, selectionProto(tree.name, pt, this)) + if (tree.name.isTypeName) checkStable(qual1.tpe, qual1.pos) + val select = typedSelect(tree, pt, qual1) + if (select.tpe ne TryDynamicCallType) select + else if (pt.isInstanceOf[PolyProto] || pt.isInstanceOf[FunProto] || pt == AssignProto) select + else typedDynamicSelect(tree, Nil, pt) + } + + def typeSelectOnType(qual: untpd.Tree)(implicit ctx: Context) = + typedSelect(untpd.cpy.Select(tree)(qual, tree.name.toTypeName), pt) + + def tryJavaSelectOnType(implicit ctx: Context): Tree = tree.qualifier match { + case Select(qual, name) => typeSelectOnType(untpd.Select(qual, name.toTypeName)) + case Ident(name) => typeSelectOnType(untpd.Ident(name.toTypeName)) + case _ => errorTree(tree, "cannot convert to type selection") // will never be printed due to fallback + } + + def selectWithFallback(fallBack: Context => Tree) = + tryAlternatively(typeSelectOnTerm(_))(fallBack) + + if (tree.qualifier.isType) { + val qual1 = typedType(tree.qualifier, selectionProto(tree.name, pt, this)) + assignType(cpy.Select(tree)(qual1, tree.name), qual1) + } + else if (ctx.compilationUnit.isJava && tree.name.isTypeName) + // SI-3120 Java uses the same syntax, A.B, to express selection from the + // value A and from the type A. We have to try both. + selectWithFallback(tryJavaSelectOnType(_)) // !!! possibly exponential bcs of qualifier retyping + else if (tree.name == nme.withFilter && tree.getAttachment(desugar.MaybeFilter).isDefined) + selectWithFallback { + implicit ctx => + typedSelect(untpd.cpy.Select(tree)(tree.qualifier, nme.filter), pt) // !!! possibly exponential bcs of qualifier retyping + } + else + typeSelectOnTerm(ctx) + } + + def typedThis(tree: untpd.This)(implicit ctx: Context): Tree = track("typedThis") { + assignType(tree) + } + + def typedSuper(tree: untpd.Super, pt: Type)(implicit ctx: Context): Tree = track("typedSuper") { + val qual1 = typed(tree.qual) + val inConstrCall = pt match { + case pt: SelectionProto if pt.name == nme.CONSTRUCTOR => true + case _ => false + } + pt match { + case pt: SelectionProto if pt.name.isTypeName => + qual1 // don't do super references for types; they are meaningless anyway + case _ => + assignType(cpy.Super(tree)(qual1, tree.mix), qual1, inConstrCall) + } + } + + def typedLiteral(tree: untpd.Literal)(implicit ctx: Context) = track("typedLiteral") { + assignType(tree) + } + + def typedNew(tree: untpd.New, pt: Type)(implicit ctx: Context) = track("typedNew") { + tree.tpt match { + case templ: untpd.Template => + import untpd._ + val x = tpnme.ANON_CLASS + val clsDef = TypeDef(x, templ).withFlags(Final) + typed(cpy.Block(tree)(clsDef :: Nil, New(Ident(x), Nil)), pt) + case _ => + var tpt1 = typedType(tree.tpt) + tpt1 = tpt1.withType(ensureAccessible(tpt1.tpe, superAccess = false, tpt1.pos)) + tpt1.tpe.dealias match { + case TypeApplications.EtaExpansion(tycon) => tpt1 = tpt1.withType(tycon) + case _ => + } + checkClassType(tpt1.tpe, tpt1.pos, traitReq = false, stablePrefixReq = true) + + tpt1 match { + case AppliedTypeTree(_, targs) => + for (targ @ TypeBoundsTree(_, _) <- targs) + ctx.error("type argument must be fully defined", targ.pos) + case _ => + } + + assignType(cpy.New(tree)(tpt1), tpt1) + // todo in a later phase: checkInstantiatable(cls, tpt1.pos) + } + } + + def typedTyped(tree: untpd.Typed, pt: Type)(implicit ctx: Context): Tree = track("typedTyped") { + /* Handles three cases: + * @param ifPat how to handle a pattern (_: T) + * @param ifExpr how to handle an expression (e: T) + * @param wildName what name `w` to use in the rewriting of + * (x: T) to (x @ (w: T)). This is either `_` or `_*`. + */ + def cases(ifPat: => Tree, ifExpr: => Tree, wildName: TermName) = tree.expr match { + case id: untpd.Ident if (ctx.mode is Mode.Pattern) && isVarPattern(id) => + if (id.name == nme.WILDCARD || id.name == nme.WILDCARD_STAR) ifPat + else { + import untpd._ + typed(Bind(id.name, Typed(Ident(wildName), tree.tpt)).withPos(id.pos), pt) + } + case _ => ifExpr + } + def ascription(tpt: Tree, isWildcard: Boolean) = { + val underlyingTreeTpe = + if (isRepeatedParamType(tpt)) TypeTree(defn.SeqType.appliedTo(pt :: Nil)) + else tpt + + val expr1 = + if (isRepeatedParamType(tpt)) tree.expr.withType(defn.SeqType.appliedTo(pt :: Nil)) + else if (isWildcard) tree.expr.withType(tpt.tpe) + else typed(tree.expr, tpt.tpe.widenSkolem) + assignType(cpy.Typed(tree)(expr1, tpt), underlyingTreeTpe) + } + if (untpd.isWildcardStarArg(tree)) + cases( + ifPat = ascription(TypeTree(defn.RepeatedParamType.appliedTo(pt)), isWildcard = true), + ifExpr = seqToRepeated(typedExpr(tree.expr, defn.SeqType)), + wildName = nme.WILDCARD_STAR) + else { + def typedTpt = checkSimpleKinded(typedType(tree.tpt)) + def handlePattern: Tree = { + val tpt1 = typedTpt + // special case for an abstract type that comes with a class tag + tpt1.tpe.dealias match { + case tref: TypeRef if !tref.symbol.isClass && !ctx.isAfterTyper => + inferImplicit(defn.ClassTagType.appliedTo(tref), + EmptyTree, tpt1.pos)(ctx.retractMode(Mode.Pattern)) match { + case SearchSuccess(arg, _, _) => + return typed(untpd.Apply(untpd.TypedSplice(arg), tree.expr), pt) + case _ => + } + case _ => + if (!ctx.isAfterTyper) tpt1.tpe.<:<(pt)(ctx.addMode(Mode.GADTflexible)) + } + ascription(tpt1, isWildcard = true) + } + cases( + ifPat = handlePattern, + ifExpr = ascription(typedTpt, isWildcard = false), + wildName = nme.WILDCARD) + } + } + + def typedNamedArg(tree: untpd.NamedArg, pt: Type)(implicit ctx: Context) = track("typedNamedArg") { + val arg1 = typed(tree.arg, pt) + assignType(cpy.NamedArg(tree)(tree.name, arg1), arg1) + } + + def typedAssign(tree: untpd.Assign, pt: Type)(implicit ctx: Context) = track("typedAssign") { + tree.lhs match { + case lhs @ Apply(fn, args) => + typed(cpy.Apply(lhs)(untpd.Select(fn, nme.update), args :+ tree.rhs), pt) + case untpd.TypedSplice(Apply(MaybePoly(Select(fn, app), targs), args)) if app == nme.apply => + val rawUpdate: untpd.Tree = untpd.Select(untpd.TypedSplice(fn), nme.update) + val wrappedUpdate = + if (targs.isEmpty) rawUpdate + else untpd.TypeApply(rawUpdate, targs map (untpd.TypedSplice(_))) + val appliedUpdate = cpy.Apply(fn)(wrappedUpdate, (args map (untpd.TypedSplice(_))) :+ tree.rhs) + typed(appliedUpdate, pt) + case lhs => + val lhsCore = typedUnadapted(lhs, AssignProto) + def lhs1 = typed(untpd.TypedSplice(lhsCore)) + def canAssign(sym: Symbol) = // allow assignments from the primary constructor to class fields + sym.is(Mutable, butNot = Accessor) || + ctx.owner.isPrimaryConstructor && !sym.is(Method) && sym.owner == ctx.owner.owner || + ctx.owner.name.isTraitSetterName || ctx.owner.isStaticConstructor + lhsCore.tpe match { + case ref: TermRef if canAssign(ref.symbol) => + assignType(cpy.Assign(tree)(lhs1, typed(tree.rhs, ref.info))) + case _ => + def reassignmentToVal = + errorTree(cpy.Assign(tree)(lhsCore, typed(tree.rhs, lhs1.tpe.widen)), + "reassignment to val") + lhsCore.tpe match { + case ref: TermRef => // todo: further conditions to impose on getter? + val pre = ref.prefix + val setterName = ref.name.setterName + val setter = pre.member(setterName) + lhsCore match { + case lhsCore: RefTree if setter.exists => + val setterTypeRaw = pre.select(setterName, setter) + val setterType = ensureAccessible(setterTypeRaw, isSuperSelection(lhsCore), tree.pos) + val lhs2 = healNonvariant( + untpd.rename(lhsCore, setterName).withType(setterType), WildcardType) + typedUnadapted(cpy.Apply(tree)(untpd.TypedSplice(lhs2), tree.rhs :: Nil)) + case _ => + reassignmentToVal + } + case TryDynamicCallType => + typedDynamicAssign(tree, pt) + case tpe => + reassignmentToVal + } + } + } + } + + def typedBlockStats(stats: List[untpd.Tree])(implicit ctx: Context): (Context, List[tpd.Tree]) = + (index(stats), typedStats(stats, ctx.owner)) + + def typedBlock(tree: untpd.Block, pt: Type)(implicit ctx: Context) = track("typedBlock") { + val (exprCtx, stats1) = typedBlockStats(tree.stats) + val ept = + if (tree.isInstanceOf[untpd.InfixOpBlock]) + // Right-binding infix operations are expanded to InfixBlocks, which may be followed by arguments. + // Example: `(a /: bs)(op)` expands to `{ val x = a; bs./:(x) } (op)` where `{...}` is an InfixBlock. + pt + else pt.notApplied + val expr1 = typedExpr(tree.expr, ept)(exprCtx) + ensureNoLocalRefs( + assignType(cpy.Block(tree)(stats1, expr1), stats1, expr1), pt, localSyms(stats1)) + } + + def escapingRefs(block: Tree, localSyms: => List[Symbol])(implicit ctx: Context): collection.Set[NamedType] = { + lazy val locals = localSyms.toSet + block.tpe namedPartsWith (tp => locals.contains(tp.symbol)) + } + + /** Check that expression's type can be expressed without references to locally defined + * symbols. The following two remedies are tried before giving up: + * 1. If the expected type of the expression is fully defined, pick it as the + * type of the result expressed by adding a type ascription. + * 2. If (1) fails, force all type variables so that the block's type is + * fully defined and try again. + */ + protected def ensureNoLocalRefs(tree: Tree, pt: Type, localSyms: => List[Symbol], forcedDefined: Boolean = false)(implicit ctx: Context): Tree = { + def ascribeType(tree: Tree, pt: Type): Tree = tree match { + case block @ Block(stats, expr) => + val expr1 = ascribeType(expr, pt) + cpy.Block(block)(stats, expr1) withType expr1.tpe // no assignType here because avoid is redundant + case _ => + Typed(tree, TypeTree(pt.simplified)) + } + val leaks = escapingRefs(tree, localSyms) + if (leaks.isEmpty) tree + else if (isFullyDefined(pt, ForceDegree.none)) ascribeType(tree, pt) + else if (!forcedDefined) { + fullyDefinedType(tree.tpe, "block", tree.pos) + val tree1 = ascribeType(tree, avoid(tree.tpe, localSyms)) + ensureNoLocalRefs(tree1, pt, localSyms, forcedDefined = true) + } else + errorTree(tree, + em"local definition of ${leaks.head.name} escapes as part of expression's type ${tree.tpe}"/*; full type: ${result.tpe.toString}"*/) + } + + def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context): Tree = track("typedIf") { + val cond1 = typed(tree.cond, defn.BooleanType) + val thenp1 = typed(tree.thenp, pt.notApplied) + val elsep1 = typed(tree.elsep orElse (untpd.unitLiteral withPos tree.pos), pt.notApplied) + val thenp2 :: elsep2 :: Nil = harmonize(thenp1 :: elsep1 :: Nil) + assignType(cpy.If(tree)(cond1, thenp2, elsep2), thenp2, elsep2) + } + + private def decomposeProtoFunction(pt: Type, defaultArity: Int)(implicit ctx: Context): (List[Type], Type) = pt match { + case _ if defn.isFunctionType(pt) => + // if expected parameter type(s) are wildcards, approximate from below. + // if expected result type is a wildcard, approximate from above. + // this can type the greatest set of admissible closures. + (pt.dealias.argTypesLo.init, pt.dealias.argTypesHi.last) + case SAMType(meth) => + val mt @ MethodType(_, paramTypes) = meth.info + (paramTypes, mt.resultType) + case _ => + (List.range(0, defaultArity) map alwaysWildcardType, WildcardType) + } + + def typedFunction(tree: untpd.Function, pt: Type)(implicit ctx: Context) = track("typedFunction") { + val untpd.Function(args, body) = tree + if (ctx.mode is Mode.Type) + typed(cpy.AppliedTypeTree(tree)( + untpd.TypeTree(defn.FunctionClass(args.length).typeRef), args :+ body), pt) + else { + val params = args.asInstanceOf[List[untpd.ValDef]] + + pt match { + case pt: TypeVar if untpd.isFunctionWithUnknownParamType(tree) => + // try to instantiate `pt` if this is possible. If it does not + // work the error will be reported later in `inferredParam`, + // when we try to infer the parameter type. + isFullyDefined(pt, ForceDegree.noBottom) + case _ => + } + + val (protoFormals, protoResult) = decomposeProtoFunction(pt, params.length) + + def refersTo(arg: untpd.Tree, param: untpd.ValDef): Boolean = arg match { + case Ident(name) => name == param.name + case _ => false + } + + /** The function body to be returned in the closure. Can become a TypedSplice + * of a typed expression if this is necessary to infer a parameter type. + */ + var fnBody = tree.body + + /** If function is of the form + * (x1, ..., xN) => f(x1, ..., XN) + * the type of `f`, otherwise NoType. (updates `fnBody` as a side effect). + */ + def calleeType: Type = fnBody match { + case Apply(expr, args) if (args corresponds params)(refersTo) => + expr match { + case untpd.TypedSplice(expr1) => + expr1.tpe + case _ => + val protoArgs = args map (_ withType WildcardType) + val callProto = FunProto(protoArgs, WildcardType, this) + val expr1 = typedExpr(expr, callProto) + fnBody = cpy.Apply(fnBody)(untpd.TypedSplice(expr1), args) + expr1.tpe + } + case _ => + NoType + } + + /** Two attempts: First, if expected type is fully defined pick this one. + * Second, if function is of the form + * (x1, ..., xN) => f(x1, ..., XN) + * and f has a method type MT, pick the corresponding parameter type in MT, + * if this one is fully defined. + * If both attempts fail, issue a "missing parameter type" error. + */ + def inferredParamType(param: untpd.ValDef, formal: Type): Type = { + if (isFullyDefined(formal, ForceDegree.noBottom)) return formal + calleeType.widen match { + case mtpe: MethodType => + val pos = params indexWhere (_.name == param.name) + if (pos < mtpe.paramTypes.length) { + val ptype = mtpe.paramTypes(pos) + if (isFullyDefined(ptype, ForceDegree.noBottom)) return ptype + } + case _ => + } + val ofFun = + if (nme.syntheticParamNames(args.length + 1) contains param.name) + i" of expanded function $tree" + else + "" + errorType(i"missing parameter type for parameter ${param.name}$ofFun, expected = $pt", param.pos) + } + + def protoFormal(i: Int): Type = + if (protoFormals.length == params.length) protoFormals(i) + else errorType(i"wrong number of parameters, expected: ${protoFormals.length}", tree.pos) + + /** Is `formal` a product type which is elementwise compatible with `params`? */ + def ptIsCorrectProduct(formal: Type) = { + val pclass = defn.ProductNType(params.length).symbol + isFullyDefined(formal, ForceDegree.noBottom) && + formal.derivesFrom(pclass) && + formal.baseArgTypes(pclass).corresponds(params) { + (argType, param) => + param.tpt.isEmpty || argType <:< typedAheadType(param.tpt).tpe + } + } + + val desugared = + if (protoFormals.length == 1 && params.length != 1 && ptIsCorrectProduct(protoFormals.head)) { + desugar.makeTupledFunction(params, fnBody) + } + else { + val inferredParams: List[untpd.ValDef] = + for ((param, i) <- params.zipWithIndex) yield + if (!param.tpt.isEmpty) param + else cpy.ValDef(param)( + tpt = untpd.TypeTree( + inferredParamType(param, protoFormal(i)).underlyingIfRepeated(isJava = false))) + + // Define result type of closure as the expected type, thereby pushing + // down any implicit searches. We do this even if the expected type is not fully + // defined, which is a bit of a hack. But it's needed to make the following work + // (see typers.scala and printers/PlainPrinter.scala for examples). + // + // def double(x: Char): String = s"$x$x" + // "abc" flatMap double + // + val resultTpt = protoResult match { + case WildcardType(_) => untpd.TypeTree() + case _ => untpd.TypeTree(protoResult) + } + val inlineable = pt.hasAnnotation(defn.InlineParamAnnot) + desugar.makeClosure(inferredParams, fnBody, resultTpt, inlineable) + } + typed(desugared, pt) + } + } + + def typedClosure(tree: untpd.Closure, pt: Type)(implicit ctx: Context): Tree = track("typedClosure") { + val env1 = tree.env mapconserve (typed(_)) + val meth1 = typedUnadapted(tree.meth) + val target = + if (tree.tpt.isEmpty) + meth1.tpe.widen match { + case mt: MethodType => + pt match { + case SAMType(meth) if !defn.isFunctionType(pt) && mt <:< meth.info => + if (!isFullyDefined(pt, ForceDegree.all)) + ctx.error(ex"result type of closure is an underspecified SAM type $pt", tree.pos) + TypeTree(pt) + case _ => + if (!mt.isDependent) EmptyTree + else throw new java.lang.Error(i"internal error: cannot turn dependent method type $mt into closure, position = ${tree.pos}, raw type = ${mt.toString}") // !!! DEBUG. Eventually, convert to an error? + } + case tp => + throw new java.lang.Error(i"internal error: closing over non-method $tp, pos = ${tree.pos}") + } + else typed(tree.tpt) + //println(i"typing closure $tree : ${meth1.tpe.widen}") + assignType(cpy.Closure(tree)(env1, meth1, target), meth1, target) + } + + def typedMatch(tree: untpd.Match, pt: Type)(implicit ctx: Context) = track("typedMatch") { + tree.selector match { + case EmptyTree => + val (protoFormals, _) = decomposeProtoFunction(pt, 1) + val unchecked = pt <:< defn.PartialFunctionType + typed(desugar.makeCaseLambda(tree.cases, protoFormals.length, unchecked) withPos tree.pos, pt) + case _ => + val sel1 = typedExpr(tree.selector) + val selType = widenForMatchSelector( + fullyDefinedType(sel1.tpe, "pattern selector", tree.pos)) + + val cases1 = typedCases(tree.cases, selType, pt.notApplied) + val cases2 = harmonize(cases1).asInstanceOf[List[CaseDef]] + assignType(cpy.Match(tree)(sel1, cases2), cases2) + } + } + + def typedCases(cases: List[untpd.CaseDef], selType: Type, pt: Type)(implicit ctx: Context) = { + + /** gadtSyms = "all type parameters of enclosing methods that appear + * non-variantly in the selector type" todo: should typevars + * which appear with variances +1 and -1 (in different + * places) be considered as well? + */ + val gadtSyms: Set[Symbol] = ctx.traceIndented(i"GADT syms of $selType", gadts) { + val accu = new TypeAccumulator[Set[Symbol]] { + def apply(tsyms: Set[Symbol], t: Type): Set[Symbol] = { + val tsyms1 = t match { + case tr: TypeRef if (tr.symbol is TypeParam) && tr.symbol.owner.isTerm && variance == 0 => + tsyms + tr.symbol + case _ => + tsyms + } + foldOver(tsyms1, t) + } + } + accu(Set.empty, selType) + } + + cases mapconserve (typedCase(_, pt, selType, gadtSyms)) + } + + /** Type a case. Overridden in ReTyper, that's why it's separate from + * typedCases. + */ + def typedCase(tree: untpd.CaseDef, pt: Type, selType: Type, gadtSyms: Set[Symbol])(implicit ctx: Context): CaseDef = track("typedCase") { + val originalCtx = ctx + + /** - replace all references to symbols associated with wildcards by their GADT bounds + * - enter all symbols introduced by a Bind in current scope + */ + val indexPattern = new TreeMap { + val elimWildcardSym = new TypeMap { + def apply(t: Type) = t match { + case ref @ TypeRef(_, tpnme.WILDCARD) if ctx.gadt.bounds.contains(ref.symbol) => + ctx.gadt.bounds(ref.symbol) + case TypeAlias(ref @ TypeRef(_, tpnme.WILDCARD)) if ctx.gadt.bounds.contains(ref.symbol) => + ctx.gadt.bounds(ref.symbol) + case _ => + mapOver(t) + } + } + override def transform(trt: Tree)(implicit ctx: Context) = + super.transform(trt.withType(elimWildcardSym(trt.tpe))) match { + case b: Bind => + if (ctx.scope.lookup(b.name) == NoSymbol) ctx.enter(b.symbol) + else ctx.error(new DuplicateBind(b, tree), b.pos) + b.symbol.info = elimWildcardSym(b.symbol.info) + b + case t => t + } + } + + def caseRest(pat: Tree)(implicit ctx: Context) = { + val pat1 = indexPattern.transform(pat) + val guard1 = typedExpr(tree.guard, defn.BooleanType) + val body1 = ensureNoLocalRefs(typedExpr(tree.body, pt), pt, ctx.scope.toList) + .ensureConforms(pt)(originalCtx) // insert a cast if body does not conform to expected type if we disregard gadt bounds + assignType(cpy.CaseDef(tree)(pat1, guard1, body1), body1) + } + + val gadtCtx = + if (gadtSyms.isEmpty) ctx + else { + val c = ctx.fresh.setFreshGADTBounds + for (sym <- gadtSyms) + if (!c.gadt.bounds.contains(sym)) + c.gadt.setBounds(sym, TypeBounds.empty) + c + } + val pat1 = typedPattern(tree.pat, selType)(gadtCtx) + caseRest(pat1)(gadtCtx.fresh.setNewScope) + } + + def typedReturn(tree: untpd.Return)(implicit ctx: Context): Return = track("typedReturn") { + def returnProto(owner: Symbol, locals: Scope): Type = + if (owner.isConstructor) defn.UnitType + else owner.info match { + case info: PolyType => + val tparams = locals.toList.takeWhile(_ is TypeParam) + assert(info.paramNames.length == tparams.length, + i"return mismatch from $owner, tparams = $tparams, locals = ${locals.toList}%, %") + info.instantiate(tparams.map(_.typeRef)).finalResultType + case info => + info.finalResultType + } + def enclMethInfo(cx: Context): (Tree, Type) = { + val owner = cx.owner + if (cx == NoContext || owner.isType) { + ctx.error("return outside method definition", tree.pos) + (EmptyTree, WildcardType) + } + else if (owner != cx.outer.owner && owner.isRealMethod) { + if (owner.isInlineMethod) + (EmptyTree, errorType(em"no explicit return allowed from inline $owner", tree.pos)) + else if (!owner.isCompleted) + (EmptyTree, errorType(em"$owner has return statement; needs result type", tree.pos)) + else { + val from = Ident(TermRef(NoPrefix, owner.asTerm)) + val proto = returnProto(owner, cx.scope) + (from, proto) + } + } + else enclMethInfo(cx.outer) + } + val (from, proto) = + if (tree.from.isEmpty) enclMethInfo(ctx) + else { + val from = tree.from.asInstanceOf[tpd.Tree] + val proto = + if (ctx.erasedTypes) from.symbol.info.finalResultType + else WildcardType // We cannot reliably detect the internal type view of polymorphic or dependent methods + // because we do not know the internal type params and method params. + // Hence no adaptation is possible, and we assume WildcardType as prototype. + (from, proto) + } + val expr1 = typedExpr(tree.expr orElse untpd.unitLiteral.withPos(tree.pos), proto) + assignType(cpy.Return(tree)(expr1, from)) + } + + def typedTry(tree: untpd.Try, pt: Type)(implicit ctx: Context): Try = track("typedTry") { + val expr1 = typed(tree.expr, pt.notApplied) + val cases1 = typedCases(tree.cases, defn.ThrowableType, pt.notApplied) + val finalizer1 = typed(tree.finalizer, defn.UnitType) + val expr2 :: cases2x = harmonize(expr1 :: cases1) + val cases2 = cases2x.asInstanceOf[List[CaseDef]] + assignType(cpy.Try(tree)(expr2, cases2, finalizer1), expr2, cases2) + } + + def typedThrow(tree: untpd.Throw)(implicit ctx: Context): Tree = track("typedThrow") { + val expr1 = typed(tree.expr, defn.ThrowableType) + Throw(expr1).withPos(tree.pos) + } + + def typedSeqLiteral(tree: untpd.SeqLiteral, pt: Type)(implicit ctx: Context): SeqLiteral = track("typedSeqLiteral") { + val proto1 = pt.elemType match { + case NoType => WildcardType + case bounds: TypeBounds => WildcardType(bounds) + case elemtp => elemtp + } + val elems1 = tree.elems mapconserve (typed(_, proto1)) + 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 typedInlined(tree: untpd.Inlined, pt: Type)(implicit ctx: Context): Inlined = { + val (exprCtx, bindings1) = typedBlockStats(tree.bindings) + val expansion1 = typed(tree.expansion, pt)(inlineContext(tree.call)(exprCtx)) + assignType(cpy.Inlined(tree)(tree.call, bindings1.asInstanceOf[List[MemberDef]], expansion1), + bindings1, expansion1) + } + + def typedTypeTree(tree: untpd.TypeTree, pt: Type)(implicit ctx: Context): TypeTree = track("typedTypeTree") { + tree match { + case tree: untpd.DerivedTypeTree => + tree.ensureCompletions + try + TypeTree(tree.derivedType(tree.attachment(untpd.OriginalSymbol))) withPos tree.pos + // btw, no need to remove the attachment. The typed + // tree is different from the untyped one, so the + // untyped tree is no longer accessed after all + // accesses with typedTypeTree are done. + catch { + case ex: NoSuchElementException => + println(s"missing OriginalSymbol for ${ctx.owner.ownersIterator.toList}") + throw ex + } + case _ => + assert(isFullyDefined(pt, ForceDegree.none)) + tree.withType(pt) + } + } + + def typedSingletonTypeTree(tree: untpd.SingletonTypeTree)(implicit ctx: Context): SingletonTypeTree = track("typedSingletonTypeTree") { + val ref1 = typedExpr(tree.ref) + checkStable(ref1.tpe, tree.pos) + assignType(cpy.SingletonTypeTree(tree)(ref1), ref1) + } + + def typedAndTypeTree(tree: untpd.AndTypeTree)(implicit ctx: Context): AndTypeTree = track("typedAndTypeTree") { + val left1 = typed(tree.left) + val right1 = typed(tree.right) + assignType(cpy.AndTypeTree(tree)(left1, right1), left1, right1) + } + + def typedOrTypeTree(tree: untpd.OrTypeTree)(implicit ctx: Context): OrTypeTree = track("typedOrTypeTree") { + val where = "in a union type" + val left1 = checkNotSingleton(typed(tree.left), where) + val right1 = checkNotSingleton(typed(tree.right), where) + assignType(cpy.OrTypeTree(tree)(left1, right1), left1, right1) + } + + def typedRefinedTypeTree(tree: untpd.RefinedTypeTree)(implicit ctx: Context): RefinedTypeTree = track("typedRefinedTypeTree") { + val tpt1 = if (tree.tpt.isEmpty) TypeTree(defn.ObjectType) else typedAheadType(tree.tpt) + val refineClsDef = desugar.refinedTypeToClass(tpt1, tree.refinements) + val refineCls = createSymbol(refineClsDef).asClass + val TypeDef(_, impl: Template) = typed(refineClsDef) + val refinements1 = impl.body + assert(tree.refinements.length == refinements1.length, s"${tree.refinements} != $refinements1") + val seen = mutable.Set[Symbol]() + for (refinement <- refinements1) { // TODO: get clarity whether we want to enforce these conditions + typr.println(s"adding refinement $refinement") + checkRefinementNonCyclic(refinement, refineCls, seen) + val rsym = refinement.symbol + if (rsym.is(Method) && rsym.allOverriddenSymbols.isEmpty) + ctx.error(i"refinement $rsym without matching type in parent $tpt1", refinement.pos) + } + assignType(cpy.RefinedTypeTree(tree)(tpt1, refinements1), tpt1, refinements1, refineCls) + } + + def typedAppliedTypeTree(tree: untpd.AppliedTypeTree)(implicit ctx: Context): Tree = track("typedAppliedTypeTree") { + val tpt1 = typed(tree.tpt, AnyTypeConstructorProto)(ctx.retractMode(Mode.Pattern)) + val tparams = tpt1.tpe.typeParams + if (tparams.isEmpty) { + ctx.error(ex"${tpt1.tpe} does not take type parameters", tree.pos) + tpt1 + } + else { + var args = tree.args + val args1 = + if (hasNamedArg(args)) typedNamedArgs(args) + else { + if (args.length != tparams.length) { + wrongNumberOfArgs(tpt1.tpe, "type", tparams, args, tree.pos) + args = args.take(tparams.length) + } + def typedArg(arg: untpd.Tree, tparam: TypeParamInfo) = { + val (desugaredArg, argPt) = + if (ctx.mode is Mode.Pattern) + (if (isVarPattern(arg)) desugar.patternVar(arg) else arg, tparam.paramBounds) + else + (arg, WildcardType) + typed(desugaredArg, argPt) + } + args.zipWithConserve(tparams)(typedArg(_, _)).asInstanceOf[List[Tree]] + } + // check that arguments conform to bounds is done in phase PostTyper + assignType(cpy.AppliedTypeTree(tree)(tpt1, args1), tpt1, args1) + } + } + + def typedPolyTypeTree(tree: untpd.PolyTypeTree)(implicit ctx: Context): Tree = track("typedPolyTypeTree") { + val PolyTypeTree(tparams, body) = tree + index(tparams) + val tparams1 = tparams.mapconserve(typed(_).asInstanceOf[TypeDef]) + val body1 = typedType(tree.body) + assignType(cpy.PolyTypeTree(tree)(tparams1, body1), tparams1, body1) + } + + def typedByNameTypeTree(tree: untpd.ByNameTypeTree)(implicit ctx: Context): ByNameTypeTree = track("typedByNameTypeTree") { + val result1 = typed(tree.result) + assignType(cpy.ByNameTypeTree(tree)(result1), result1) + } + + /** Define a new symbol associated with a Bind or pattern wildcard and + * make it gadt narrowable. + */ + private def newPatternBoundSym(name: Name, info: Type, pos: Position)(implicit ctx: Context) = { + val flags = if (name.isTypeName) BindDefinedType else EmptyFlags + val sym = ctx.newSymbol(ctx.owner, name, flags | Case, info, coord = pos) + if (name.isTypeName) ctx.gadt.setBounds(sym, info.bounds) + sym + } + + def typedTypeBoundsTree(tree: untpd.TypeBoundsTree)(implicit ctx: Context): TypeBoundsTree = track("typedTypeBoundsTree") { + val TypeBoundsTree(lo, hi) = desugar.typeBoundsTree(tree) + val lo1 = typed(lo) + val hi1 = typed(hi) + val tree1 = assignType(cpy.TypeBoundsTree(tree)(lo1, hi1), lo1, hi1) + if (ctx.mode.is(Mode.Pattern)) { + // Associate a pattern-bound type symbol with the wildcard. + // The bounds of the type symbol can be constrained when comparing a pattern type + // with an expected type in typedTyped. The type symbol is eliminated once + // the enclosing pattern has been typechecked; see `indexPattern` in `typedCase`. + val wildcardSym = newPatternBoundSym(tpnme.WILDCARD, tree1.tpe, tree.pos) + tree1.withType(wildcardSym.typeRef) + } + else tree1 + } + + def typedBind(tree: untpd.Bind, pt: Type)(implicit ctx: Context): Tree = track("typedBind") { + val pt1 = fullyDefinedType(pt, "pattern variable", tree.pos) + val body1 = typed(tree.body, pt1) + typr.println(i"typed bind $tree pt = $pt1 bodytpe = ${body1.tpe}") + body1 match { + case UnApply(fn, Nil, arg :: Nil) if tree.body.isInstanceOf[untpd.Typed] => + // A typed pattern `x @ (_: T)` with an implicit `ctag: ClassTag[T]` + // was rewritten to `x @ ctag(_)`. + // Rewrite further to `ctag(x @ _)` + assert(fn.symbol.owner == defn.ClassTagClass) + tpd.cpy.UnApply(body1)(fn, Nil, + typed(untpd.Bind(tree.name, arg).withPos(tree.pos), arg.tpe) :: Nil) + case _ => + val sym = newPatternBoundSym(tree.name, body1.tpe, tree.pos) + assignType(cpy.Bind(tree)(tree.name, body1), sym) + } + } + + def typedAlternative(tree: untpd.Alternative, pt: Type)(implicit ctx: Context): Alternative = track("typedAlternative") { + val trees1 = tree.trees mapconserve (typed(_, pt)) + assignType(cpy.Alternative(tree)(trees1), trees1) + } + + def completeAnnotations(mdef: untpd.MemberDef, sym: Symbol)(implicit ctx: Context): Unit = { + // necessary to force annotation trees to be computed. + sym.annotations.foreach(_.ensureCompleted) + val annotCtx = ctx.outersIterator.dropWhile(_.owner == sym).next + // necessary in order to mark the typed ahead annotations as definitely typed: + untpd.modsDeco(mdef).mods.annotations.foreach(typedAnnotation(_)(annotCtx)) + } + + def typedAnnotation(annot: untpd.Tree)(implicit ctx: Context): Tree = track("typedAnnotation") { + typed(annot, defn.AnnotationType) + } + + def typedValDef(vdef: untpd.ValDef, sym: Symbol)(implicit ctx: Context) = track("typedValDef") { + val ValDef(name, tpt, _) = vdef + completeAnnotations(vdef, sym) + val tpt1 = checkSimpleKinded(typedType(tpt)) + val rhs1 = vdef.rhs match { + case rhs @ Ident(nme.WILDCARD) => rhs withType tpt1.tpe + case rhs => typedExpr(rhs, tpt1.tpe) + } + val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym) + if (sym.is(Inline, butNot = DeferredOrParamAccessor)) + checkInlineConformant(rhs1, em"right-hand side of inline $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).pos.start), "@volatile ") + } + + def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(implicit ctx: Context) = track("typedDefDef") { + val DefDef(name, tparams, vparamss, tpt, _) = ddef + completeAnnotations(ddef, sym) + val tparams1 = tparams mapconserve (typed(_).asInstanceOf[TypeDef]) + val vparamss1 = vparamss nestedMapconserve (typed(_).asInstanceOf[ValDef]) + if (sym is Implicit) checkImplicitParamsNotSingletons(vparamss1) + var tpt1 = checkSimpleKinded(typedType(tpt)) + + var rhsCtx = ctx + if (sym.isConstructor && !sym.isPrimaryConstructor && tparams1.nonEmpty) { + // for secondary constructors we need a context that "knows" + // that their type parameters are aliases of the class type parameters. + // See pos/i941.scala + rhsCtx = ctx.fresh.setFreshGADTBounds + (tparams1, sym.owner.typeParams).zipped.foreach ((tdef, tparam) => + rhsCtx.gadt.setBounds(tdef.symbol, TypeAlias(tparam.typeRef))) + } + val rhs1 = typedExpr(ddef.rhs, tpt1.tpe)(rhsCtx) + + // Overwrite inline body to make sure it is not evaluated twice + if (sym.isInlineMethod) Inliner.registerInlineInfo(sym, _ => rhs1) + + if (sym.isAnonymousFunction) { + // If we define an anonymous function, make sure the return type does not + // refer to parameters. This is necessary because closure types are + // function types so no dependencies on parameters are allowed. + tpt1 = tpt1.withType(avoid(tpt1.tpe, vparamss1.flatMap(_.map(_.symbol)))) + } + + assignType(cpy.DefDef(ddef)(name, tparams1, vparamss1, tpt1, rhs1), sym) + //todo: make sure dependent method types do not depend on implicits or by-name params + } + + def typedTypeDef(tdef: untpd.TypeDef, sym: Symbol)(implicit ctx: Context): Tree = track("typedTypeDef") { + val TypeDef(name, rhs) = tdef + completeAnnotations(tdef, sym) + val rhs1 = tdef.rhs match { + case rhs @ PolyTypeTree(tparams, body) => + val tparams1 = tparams.map(typed(_)).asInstanceOf[List[TypeDef]] + val body1 = typedType(body) + assignType(cpy.PolyTypeTree(rhs)(tparams1, body1), tparams1, body1) + case rhs => + typedType(rhs) + } + assignType(cpy.TypeDef(tdef)(name, rhs1), sym) + } + + def typedClassDef(cdef: untpd.TypeDef, cls: ClassSymbol)(implicit ctx: Context) = track("typedClassDef") { + val TypeDef(name, impl @ Template(constr, parents, self, _)) = cdef + val superCtx = ctx.superCallContext + + /** If `ref` is an implicitly parameterized trait, pass an implicit argument list. + * Otherwise, if `ref` is a parameterized trait, error. + * Note: Traits and classes currently always have at least an empty parameter list () + * before the implicit parameters (this is inserted if not given in source). + * We skip this parameter list when deciding whether a trait is parameterless or not. + * @param ref The tree referring to the (parent) trait + * @param psym Its type symbol + * @param cinfo The info of its constructor + */ + def maybeCall(ref: Tree, psym: Symbol, cinfo: Type): Tree = cinfo match { + case cinfo: PolyType => + maybeCall(ref, psym, cinfo.resultType) + case cinfo @ MethodType(Nil, _) if cinfo.resultType.isInstanceOf[ImplicitMethodType] => + val icall = New(ref).select(nme.CONSTRUCTOR).appliedToNone + typedExpr(untpd.TypedSplice(icall))(superCtx) + case cinfo @ MethodType(Nil, _) if !cinfo.resultType.isInstanceOf[MethodType] => + ref + case cinfo: MethodType => + if (!ctx.erasedTypes) { // after constructors arguments are passed in super call. + typr.println(i"constr type: $cinfo") + ctx.error(em"parameterized $psym lacks argument list", ref.pos) + } + ref + case _ => + ref + } + + def typedParent(tree: untpd.Tree): Tree = + if (tree.isType) { + val result = typedType(tree)(superCtx) + val psym = result.tpe.typeSymbol + if (psym.is(Trait) && !cls.is(Trait) && !cls.superClass.isSubClass(psym)) + maybeCall(result, psym, psym.primaryConstructor.info) + else + result + } + else { + val result = typedExpr(tree)(superCtx) + checkParentCall(result, cls) + result + } + + completeAnnotations(cdef, cls) + val constr1 = typed(constr).asInstanceOf[DefDef] + val parentsWithClass = ensureFirstIsClass(parents mapconserve typedParent, cdef.pos.toSynthetic) + val parents1 = ensureConstrCall(cls, parentsWithClass)(superCtx) + val self1 = typed(self)(ctx.outer).asInstanceOf[ValDef] // outer context where class members are not visible + val dummy = localDummy(cls, impl) + val body1 = typedStats(impl.body, dummy)(inClassContext(self1.symbol)) + + // Expand comments and type usecases + cookComments(body1.map(_.symbol), self1.symbol)(localContext(cdef, cls).setNewScope) + + checkNoDoubleDefs(cls) + val impl1 = cpy.Template(impl)(constr1, parents1, self1, body1) + .withType(dummy.nonMemberTermRef) + checkVariance(impl1) + if (!cls.is(AbstractOrTrait) && !ctx.isAfterTyper) checkRealizableBounds(cls.typeRef, cdef.namePos) + val cdef1 = assignType(cpy.TypeDef(cdef)(name, impl1), cls) + if (ctx.phase.isTyper && cdef1.tpe.derivesFrom(defn.DynamicClass) && !ctx.dynamicsEnabled) { + val isRequired = parents1.exists(_.tpe.isRef(defn.DynamicClass)) + ctx.featureWarning(nme.dynamics.toString, "extension of type scala.Dynamic", isScala2Feature = true, + cls, isRequired, cdef.pos) + } + cdef1 + + // todo later: check that + // 1. If class is non-abstract, it is instantiatable: + // - self type is s supertype of own type + // - all type members have consistent bounds + // 2. all private type members have consistent bounds + // 3. Types do not override classes. + // 4. Polymorphic type defs override nothing. + } + + /** Ensure that the first type in a list of parent types Ps points to a non-trait class. + * If that's not already the case, add one. The added class type CT is determined as follows. + * First, let C be the unique class such that + * - there is a parent P_i such that P_i derives from C, and + * - for every class D: If some parent P_j, j <= i derives from D, then C derives from D. + * Then, let CT be the smallest type which + * - has C as its class symbol, and + * - for all parents P_i: If P_i derives from C then P_i <:< CT. + */ + def ensureFirstIsClass(parents: List[Type])(implicit ctx: Context): List[Type] = { + def realClassParent(cls: Symbol): ClassSymbol = + if (!cls.isClass) defn.ObjectClass + else if (!(cls is Trait)) cls.asClass + else cls.asClass.classParents match { + case parentRef :: _ => realClassParent(parentRef.symbol) + case nil => defn.ObjectClass + } + def improve(candidate: ClassSymbol, parent: Type): ClassSymbol = { + val pcls = realClassParent(parent.classSymbol) + if (pcls derivesFrom candidate) pcls else candidate + } + parents match { + case p :: _ if p.classSymbol.isRealClass => parents + case _ => + val pcls = (defn.ObjectClass /: parents)(improve) + typr.println(i"ensure first is class $parents%, % --> ${parents map (_ baseTypeWithArgs pcls)}%, %") + val ptype = ctx.typeComparer.glb( + defn.ObjectType :: (parents map (_ baseTypeWithArgs pcls))) + ptype :: parents + } + } + + /** Ensure that first parent tree refers to a real class. */ + def ensureFirstIsClass(parents: List[Tree], pos: Position)(implicit ctx: Context): List[Tree] = parents match { + case p :: ps if p.tpe.classSymbol.isRealClass => parents + case _ => + // add synthetic class type + val first :: _ = ensureFirstIsClass(parents.tpes) + TypeTree(checkFeasible(first, pos, em"\n in inferred parent $first")).withPos(pos) :: parents + } + + /** If this is a real class, make sure its first parent is a + * constructor call. Cannot simply use a type. Overridden in ReTyper. + */ + def ensureConstrCall(cls: ClassSymbol, parents: List[Tree])(implicit ctx: Context): List[Tree] = { + val firstParent :: otherParents = parents + if (firstParent.isType && !(cls is Trait) && !cls.is(JavaDefined)) + typed(untpd.New(untpd.TypedSplice(firstParent), Nil)) :: otherParents + else parents + } + + /** Overridden in retyper */ + def checkVariance(tree: Tree)(implicit ctx: Context) = VarianceChecker.check(tree) + + def localDummy(cls: ClassSymbol, impl: untpd.Template)(implicit ctx: Context): Symbol = + ctx.newLocalDummy(cls, impl.pos) + + def typedImport(imp: untpd.Import, sym: Symbol)(implicit ctx: Context): Import = track("typedImport") { + val expr1 = typedExpr(imp.expr, AnySelectionProto) + checkStable(expr1.tpe, imp.expr.pos) + if (!ctx.isAfterTyper) checkRealizable(expr1.tpe, imp.expr.pos) + assignType(cpy.Import(imp)(expr1, imp.selectors), sym) + } + + def typedPackageDef(tree: untpd.PackageDef)(implicit ctx: Context): Tree = track("typedPackageDef") { + val pid1 = typedExpr(tree.pid, AnySelectionProto)(ctx.addMode(Mode.InPackageClauseName)) + val pkg = pid1.symbol + + // Package will not exist if a duplicate type has already been entered, see + // `tests/neg/1708.scala`, else branch's error message should be supressed + if (pkg.exists) { + val packageContext = + if (pkg is Package) ctx.fresh.setOwner(pkg.moduleClass).setTree(tree) + else { + ctx.error(em"$pkg is already defined, cannot be a package", tree.pos) + ctx + } + val stats1 = typedStats(tree.stats, pkg.moduleClass)(packageContext) + cpy.PackageDef(tree)(pid1.asInstanceOf[RefTree], stats1) withType pkg.valRef + } else errorTree(tree, i"package ${tree.pid.name} does not exist") + } + + def typedAnnotated(tree: untpd.Annotated, pt: Type)(implicit ctx: Context): Tree = track("typedAnnotated") { + val annot1 = typedExpr(tree.annot, defn.AnnotationType) + val arg1 = typed(tree.arg, pt) + if (ctx.mode is Mode.Type) + assignType(cpy.Annotated(tree)(arg1, annot1), arg1, annot1) + else { + val tpt = TypeTree(AnnotatedType(arg1.tpe.widen, Annotation(annot1))) + assignType(cpy.Typed(tree)(arg1, tpt), tpt) + } + } + + def typedTypedSplice(tree: untpd.TypedSplice)(implicit ctx: Context): Tree = + tree.tree match { + case tree1: TypeTree => tree1 // no change owner necessary here ... + case tree1: Ident => tree1 // ... or here, since these trees cannot contain bindings + case tree1 => + if (ctx.owner ne tree.owner) tree1.changeOwner(tree.owner, ctx.owner) + else tree1 + } + + + 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(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) + } + res + } + + /** Retrieve symbol attached to given tree */ + protected def retrieveSym(tree: untpd.Tree)(implicit ctx: Context) = tree.removeAttachment(SymOfTree) match { + case Some(sym) => + sym.ensureCompleted() + sym + case none => + NoSymbol + } + + /** A fresh local context with given tree and owner. + * Owner might not exist (can happen for self valdefs), in which case + * no owner is set in result context + */ + protected def localContext(tree: untpd.Tree, owner: Symbol)(implicit ctx: Context): FreshContext = { + val freshCtx = ctx.fresh.setTree(tree) + if (owner.exists) freshCtx.setOwner(owner) else freshCtx + } + + protected def localTyper(sym: Symbol): Typer = nestedTyper.remove(sym).get + + def typedUnadapted(initTree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): Tree = { + record("typedUnadapted") + val xtree = expanded(initTree) + xtree.removeAttachment(TypedAhead) match { + case Some(ttree) => ttree + case none => + + def typedNamed(tree: untpd.NameTree, pt: Type)(implicit ctx: Context): Tree = { + val sym = retrieveSym(xtree) + tree match { + case tree: untpd.Ident => typedIdent(tree, pt) + case tree: untpd.Select => typedSelect(tree, pt) + case tree: untpd.Bind => typedBind(tree, pt) + case tree: untpd.ValDef => + if (tree.isEmpty) tpd.EmptyValDef + else typedValDef(tree, sym)(localContext(tree, sym).setNewScope) + case tree: untpd.DefDef => + val typer1 = localTyper(sym) + typer1.typedDefDef(tree, sym)(localContext(tree, sym).setTyper(typer1)) + case tree: untpd.TypeDef => + if (tree.isClassDef) + typedClassDef(tree, sym.asClass)(localContext(tree, sym).setMode(ctx.mode &~ Mode.InSuperCall)) + else + typedTypeDef(tree, sym)(localContext(tree, sym).setNewScope) + case _ => typedUnadapted(desugar(tree), pt) + } + } + + def typedUnnamed(tree: untpd.Tree): Tree = tree match { + case tree: untpd.Apply => + if (ctx.mode is Mode.Pattern) typedUnApply(tree, pt) else typedApply(tree, pt) + case tree: untpd.This => typedThis(tree) + case tree: untpd.Literal => typedLiteral(tree) + case tree: untpd.New => typedNew(tree, pt) + case tree: untpd.Typed => typedTyped(tree, pt) + case tree: untpd.NamedArg => typedNamedArg(tree, pt) + case tree: untpd.Assign => typedAssign(tree, pt) + case tree: untpd.Block => typedBlock(desugar.block(tree), pt)(ctx.fresh.setNewScope) + case tree: untpd.If => typedIf(tree, pt) + case tree: untpd.Function => typedFunction(tree, pt) + case tree: untpd.Closure => typedClosure(tree, pt) + case tree: untpd.Match => typedMatch(tree, pt) + case tree: untpd.Return => typedReturn(tree) + case tree: untpd.Try => typedTry(tree, pt) + case tree: untpd.Throw => typedThrow(tree) + case tree: untpd.TypeApply => typedTypeApply(tree, pt) + case tree: untpd.Super => typedSuper(tree, pt) + case tree: untpd.SeqLiteral => typedSeqLiteral(tree, pt) + case tree: untpd.Inlined => typedInlined(tree, pt) + case tree: untpd.TypeTree => typedTypeTree(tree, pt) + case tree: untpd.SingletonTypeTree => typedSingletonTypeTree(tree) + case tree: untpd.AndTypeTree => typedAndTypeTree(tree) + case tree: untpd.OrTypeTree => typedOrTypeTree(tree) + case tree: untpd.RefinedTypeTree => typedRefinedTypeTree(tree) + case tree: untpd.AppliedTypeTree => typedAppliedTypeTree(tree) + case tree: untpd.PolyTypeTree => typedPolyTypeTree(tree)(localContext(tree, NoSymbol).setNewScope) + case tree: untpd.ByNameTypeTree => typedByNameTypeTree(tree) + case tree: untpd.TypeBoundsTree => typedTypeBoundsTree(tree) + case tree: untpd.Alternative => typedAlternative(tree, pt) + case tree: untpd.PackageDef => typedPackageDef(tree) + case tree: untpd.Annotated => typedAnnotated(tree, pt) + case tree: untpd.TypedSplice => typedTypedSplice(tree) + case tree: untpd.UnApply => typedUnApply(tree, pt) + case tree @ untpd.PostfixOp(qual, nme.WILDCARD) => typedAsFunction(tree, pt) + case untpd.EmptyTree => tpd.EmptyTree + case _ => typedUnadapted(desugar(tree), pt) + } + + xtree match { + case xtree: untpd.NameTree => typedNamed(encodeName(xtree), pt) + case xtree: untpd.Import => typedImport(xtree, retrieveSym(xtree)) + case xtree => typedUnnamed(xtree) + } + } + } + + protected def encodeName(tree: untpd.NameTree)(implicit ctx: Context): untpd.NameTree = + untpd.rename(tree, tree.name.encode) + + def typed(tree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): Tree = /*>|>*/ ctx.traceIndented (i"typing $tree", typr, show = true) /*<|<*/ { + assertPositioned(tree) + try adapt(typedUnadapted(tree, pt), pt, tree) + catch { + case ex: CyclicReference => errorTree(tree, cyclicErrorMsg(ex)) + case ex: TypeError => errorTree(tree, ex.getMessage) + } + } + + def typedTrees(trees: List[untpd.Tree])(implicit ctx: Context): List[Tree] = + trees mapconserve (typed(_)) + + def typedStats(stats: List[untpd.Tree], exprOwner: Symbol)(implicit ctx: Context): List[tpd.Tree] = { + val buf = new mutable.ListBuffer[Tree] + @tailrec def traverse(stats: List[untpd.Tree])(implicit ctx: Context): List[Tree] = stats match { + case (imp: untpd.Import) :: rest => + val imp1 = typed(imp) + buf += imp1 + traverse(rest)(importContext(imp1.symbol, imp.selectors)) + case (mdef: untpd.DefTree) :: rest => + mdef.removeAttachment(ExpandedTree) match { + case Some(xtree) => + traverse(xtree :: rest) + case none => + typed(mdef) match { + case mdef1: DefDef if Inliner.hasBodyToInline(mdef1.symbol) => + buf ++= inlineExpansion(mdef1) + case mdef1 => + buf += mdef1 + } + traverse(rest) + } + case Thicket(stats) :: rest => + traverse(stats ++ rest) + case stat :: rest => + buf += typed(stat)(ctx.exprContext(stat, exprOwner)) + traverse(rest) + case nil => + buf.toList + } + traverse(stats) + } + + /** Given an inline method `mdef`, the method rewritten so that its body + * uses accessors to access non-public members, followed by the accessor definitions. + * Overwritten in Retyper to return `mdef` unchanged. + */ + protected def inlineExpansion(mdef: DefDef)(implicit ctx: Context): List[Tree] = + tpd.cpy.DefDef(mdef)(rhs = Inliner.bodyToInline(mdef.symbol)) :: + Inliner.removeInlineAccessors(mdef.symbol) + + def typedExpr(tree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): Tree = + typed(tree, pt)(ctx retractMode Mode.PatternOrType) + def typedType(tree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): Tree = // todo: retract mode between Type and Pattern? + typed(tree, pt)(ctx addMode Mode.Type) + def typedPattern(tree: untpd.Tree, selType: Type = WildcardType)(implicit ctx: Context): Tree = + typed(tree, selType)(ctx addMode Mode.Pattern) + + def tryEither[T](op: Context => T)(fallBack: (T, TyperState) => T)(implicit ctx: Context) = { + val nestedCtx = ctx.fresh.setNewTyperState + val result = op(nestedCtx) + if (nestedCtx.reporter.hasErrors) + fallBack(result, nestedCtx.typerState) + else { + nestedCtx.typerState.commit() + result + } + } + + /** Try `op1`, if there are errors, try `op2`, if `op2` also causes errors, fall back + * to errors and result of `op1`. + */ + def tryAlternatively[T](op1: Context => T)(op2: Context => T)(implicit ctx: Context): T = + tryEither(op1) { (failedVal, failedState) => + tryEither(op2) { (_, _) => + failedState.commit + failedVal + } + } + + /** Add apply node or implicit conversions. Two strategies are tried, and the first + * that is successful is picked. If neither of the strategies are successful, continues with + * `fallBack`. + * + * 1st strategy: Try to insert `.apply` so that the result conforms to prototype `pt`. + * 2nd strategy: If tree is a select `qual.name`, try to insert an implicit conversion + * around the qualifier part `qual` so that the result conforms to the expected type + * with wildcard result type. + */ + def tryInsertApplyOrImplicit(tree: Tree, pt: ProtoType)(fallBack: (Tree, TyperState) => Tree)(implicit ctx: Context): Tree = + tryEither { implicit ctx => + val sel = typedSelect(untpd.Select(untpd.TypedSplice(tree), nme.apply), pt) + if (sel.tpe.isError) sel else adapt(sel, pt) + } { (failedTree, failedState) => + tryInsertImplicitOnQualifier(tree, pt).getOrElse(fallBack(failedTree, failedState)) + } + + /** If this tree is a select node `qual.name`, try to insert an implicit conversion + * `c` around `qual` so that `c(qual).name` conforms to `pt`. + */ + def tryInsertImplicitOnQualifier(tree: Tree, pt: Type)(implicit ctx: Context): Option[Tree] = ctx.traceIndented(i"try insert impl on qualifier $tree $pt") { + tree match { + case Select(qual, name) => + val qualProto = SelectionProto(name, pt, NoViewsAllowed) + tryEither { implicit ctx => + val qual1 = adaptInterpolated(qual, qualProto, EmptyTree) + if ((qual eq qual1) || ctx.reporter.hasErrors) None + else Some(typed(cpy.Select(tree)(untpd.TypedSplice(qual1), name), pt)) + } { (_, _) => None + } + case _ => None + } + } + + def adapt(tree: Tree, pt: Type, original: untpd.Tree = untpd.EmptyTree)(implicit ctx: Context): Tree = /*>|>*/ track("adapt") /*<|<*/ { + /*>|>*/ ctx.traceIndented(i"adapting $tree of type ${tree.tpe} to $pt", typr, show = true) /*<|<*/ { + if (tree.isDef) interpolateUndetVars(tree, tree.symbol) + else if (!tree.tpe.widen.isInstanceOf[MethodOrPoly]) interpolateUndetVars(tree, NoSymbol) + tree.overwriteType(tree.tpe.simplified) + adaptInterpolated(tree, pt, original) + } + } + + /** (-1) For expressions with annotated types, let AnnotationCheckers decide what to do + * (0) Convert expressions with constant types to literals (unless in interactive/scaladoc mode) + */ + + /** Perform the following adaptations of expression, pattern or type `tree` wrt to + * given prototype `pt`: + * (1) Resolve overloading + * (2) Apply parameterless functions + * (3) Apply polymorphic types to fresh instances of their type parameters and + * store these instances in context.undetparams, + * unless followed by explicit type application. + * (4) Do the following to unapplied methods used as values: + * (4.1) If the method has only implicit parameters pass implicit arguments + * (4.2) otherwise, if `pt` is a function type and method is not a constructor, + * convert to function by eta-expansion, + * (4.3) otherwise, if the method is nullary with a result type compatible to `pt` + * and it is not a constructor, apply it to () + * otherwise issue an error + * (5) Convert constructors in a pattern as follows: + * (5.1) If constructor refers to a case class factory, set tree's type to the unique + * instance of its primary constructor that is a subtype of the expected type. + * (5.2) If constructor refers to an extractor, convert to application of + * unapply or unapplySeq method. + * + * (6) Convert all other types to TypeTree nodes. + * (7) When in TYPEmode but not FUNmode or HKmode, check that types are fully parameterized + * (7.1) In HKmode, higher-kinded types are allowed, but they must have the expected kind-arity + * (8) When in both EXPRmode and FUNmode, add apply method calls to values of object type. + * (9) If there are undetermined type variables and not POLYmode, infer expression instance + * Then, if tree's type is not a subtype of expected type, try the following adaptations: + * (10) If the expected type is Byte, Short or Char, and the expression + * is an integer fitting in the range of that type, convert it to that type. + * (11) Widen numeric literals to their expected type, if necessary + * (12) When in mode EXPRmode, convert E to { E; () } if expected type is scala.Unit. + * (13) When in mode EXPRmode, apply AnnotationChecker conversion if expected type is annotated. + * (14) When in mode EXPRmode, apply a view + * If all this fails, error + */ + def adaptInterpolated(tree: Tree, pt: Type, original: untpd.Tree)(implicit ctx: Context): Tree = { + + assert(pt.exists) + + def methodStr = err.refStr(methPart(tree).tpe) + + def missingArgs = errorTree(tree, + em"""missing arguments for $methodStr + |follow this method with `_' if you want to treat it as a partially applied function""") + + def adaptOverloaded(ref: TermRef) = { + val altDenots = ref.denot.alternatives + typr.println(i"adapt overloaded $ref with alternatives ${altDenots map (_.info)}%, %") + val alts = altDenots map (alt => + TermRef.withSigAndDenot(ref.prefix, ref.name, alt.info.signature, alt)) + def expectedStr = err.expectedTypeStr(pt) + resolveOverloaded(alts, pt) match { + case alt :: Nil => + adapt(tree.withType(alt), pt, original) + case Nil => + def noMatches = + errorTree(tree, + em"""none of the ${err.overloadedAltsStr(altDenots)} + |match $expectedStr""") + def hasEmptyParams(denot: SingleDenotation) = denot.info.paramTypess == ListOfNil + pt match { + case pt: FunProto => + tryInsertApplyOrImplicit(tree, pt)((_, _) => noMatches) + case _ => + if (altDenots exists (_.info.paramTypess == ListOfNil)) + typed(untpd.Apply(untpd.TypedSplice(tree), Nil), pt) + else + noMatches + } + case alts => + val remainingDenots = alts map (_.denot.asInstanceOf[SingleDenotation]) + def all = if (remainingDenots.length == 2) "both" else "all" + errorTree(tree, + em"""Ambiguous overload. The ${err.overloadedAltsStr(remainingDenots)} + |$all match $expectedStr""") + } + } + + def isUnary(tp: Type): Boolean = tp match { + case tp: MethodicType => + tp.firstParamTypes match { + case ptype :: Nil => !ptype.isRepeatedParam + case _ => false + } + case tp: TermRef => + tp.denot.alternatives.forall(alt => isUnary(alt.info)) + case _ => + false + } + + def adaptToArgs(wtp: Type, pt: FunProto): Tree = wtp match { + case _: MethodType | _: PolyType => + if (pt.args.lengthCompare(1) > 0 && isUnary(wtp) && ctx.canAutoTuple) + adaptInterpolated(tree, pt.tupled, original) + else + tree + case _ => tryInsertApplyOrImplicit(tree, pt) { + val more = tree match { + case Apply(_, _) => " more" + case _ => "" + } + (_, _) => errorTree(tree, em"$methodStr does not take$more parameters") + } + } + + /** If `tp` is a TypeVar which is fully constrained (i.e. its upper bound `hi` conforms + * to its lower bound `lo`), replace `tp` by `hi`. This is necessary to + * keep the right constraints for some implicit search problems. The paradigmatic case + * is `implicitNums.scala`. Without the healing done in `followAlias`, we cannot infer + * implicitly[_3], where _2 is the typelevel number 3. The problem here is that if a + * prototype is, say, Succ[Succ[Zero]], we can infer that it's argument type is Succ[Zero]. + * But if the prototype is N? >: Succ[Succ[Zero]] <: Succ[Succ[Zero]], the same + * decomposition does not work - we'd get a N?#M where M is the element type name of Succ + * instead. + */ + def followAlias(tp: Type)(implicit ctx: Context): Type = { + val constraint = ctx.typerState.constraint + def inst(tp: Type): Type = tp match { + case TypeBounds(lo, hi) + if (lo eq hi) || (hi <:< lo)(ctx.fresh.setExploreTyperState) => + inst(lo) + case tp: PolyParam => + constraint.typeVarOfParam(tp).orElse(tp) + case _ => tp + } + tp match { + case tp: TypeVar if constraint.contains(tp) => inst(constraint.entry(tp.origin)) + case _ => tp + } + } + + def adaptNoArgs(wtp: Type): Tree = wtp match { + case wtp: ExprType => + adaptInterpolated(tree.withType(wtp.resultType), pt, original) + case wtp: ImplicitMethodType if constrainResult(wtp, followAlias(pt)) => + val tvarsToInstantiate = tvarsInParams(tree) + wtp.paramTypes.foreach(instantiateSelected(_, tvarsToInstantiate)) + val constr = ctx.typerState.constraint + def addImplicitArgs(implicit ctx: Context) = { + val errors = new mutable.ListBuffer[() => String] + def implicitArgError(msg: => String) = { + errors += (() => msg) + EmptyTree + } + def issueErrors() = { + for (err <- errors) ctx.error(err(), tree.pos.endPos) + tree.withType(wtp.resultType) + } + val args = (wtp.paramNames, wtp.paramTypes).zipped map { (pname, formal) => + def implicitArgError(msg: String => String) = + errors += (() => msg(em"parameter $pname of $methodStr")) + inferImplicitArg(formal, implicitArgError, tree.pos.endPos) + } + if (errors.nonEmpty) { + // If there are several arguments, some arguments might already + // have influenced the context, binding variables, but later ones + // might fail. In that case the constraint needs to be reset. + ctx.typerState.constraint = constr + + // If method has default params, fall back to regular application + // where all inferred implicits are passed as named args. + if (tree.symbol.hasDefaultParams) { + val namedArgs = (wtp.paramNames, args).zipped.flatMap { (pname, arg) => + arg match { + case EmptyTree => Nil + case _ => untpd.NamedArg(pname, untpd.TypedSplice(arg)) :: Nil + } + } + tryEither { implicit ctx => + typed(untpd.Apply(untpd.TypedSplice(tree), namedArgs), pt) + } { (_, _) => + issueErrors() + } + } else issueErrors() + } + else adapt(tpd.Apply(tree, args), pt) + } + if ((pt eq WildcardType) || original.isEmpty) addImplicitArgs(argCtx(tree)) + else + ctx.typerState.tryWithFallback(addImplicitArgs(argCtx(tree))) { + adapt(typed(original, WildcardType), pt, EmptyTree) + } + case wtp: MethodType if !pt.isInstanceOf[SingletonType] => + val arity = + if (defn.isFunctionType(pt)) + if (!isFullyDefined(pt, ForceDegree.none) && isFullyDefined(wtp, ForceDegree.none)) + // if method type is fully defined, but expected type is not, + // prioritize method parameter types as parameter types of the eta-expanded closure + 0 + else defn.functionArity(pt) + else if (pt eq AnyFunctionProto) wtp.paramTypes.length + else -1 + if (arity >= 0 && !tree.symbol.isConstructor) + typed(etaExpand(tree, wtp, arity), pt) + else if (wtp.paramTypes.isEmpty) + adaptInterpolated(tpd.Apply(tree, Nil), pt, EmptyTree) + else if (wtp.isImplicit) + err.typeMismatch(tree, pt) + else + missingArgs + case _ => + ctx.typeComparer.GADTused = false + if (ctx.mode is Mode.Pattern) { + tree match { + case _: RefTree | _: Literal if !isVarPattern(tree) => + checkCanEqual(pt, wtp, tree.pos)(ctx.retractMode(Mode.Pattern)) + case _ => + } + tree + } + else if (tree.tpe <:< pt) { + if (pt.hasAnnotation(defn.InlineParamAnnot)) + checkInlineConformant(tree, "argument to inline parameter") + if (Inliner.hasBodyToInline(tree.symbol) && + !ctx.owner.ownersIterator.exists(_.isInlineMethod) && + !ctx.settings.YnoInline.value && + !ctx.isAfterTyper) + adapt(Inliner.inlineCall(tree, pt), pt) + else if (ctx.typeComparer.GADTused && pt.isValueType) + // Insert an explicit cast, so that -Ycheck in later phases succeeds. + // I suspect, but am not 100% sure that this might affect inferred types, + // if the expected type is a supertype of the GADT bound. It would be good to come + // up with a test case for this. + tree.asInstance(pt) + else + tree + } + else if (wtp.isInstanceOf[MethodType]) missingArgs + else { + typr.println(i"adapt to subtype ${tree.tpe} !<:< $pt") + //typr.println(TypeComparer.explained(implicit ctx => tree.tpe <:< pt)) + adaptToSubType(wtp) + } + } + /** Adapt an expression of constant type to a different constant type `tpe`. */ + def adaptConstant(tree: Tree, tpe: ConstantType): Tree = { + def lit = Literal(tpe.value).withPos(tree.pos) + tree match { + case Literal(c) => lit + case tree @ Block(stats, expr) => tpd.cpy.Block(tree)(stats, adaptConstant(expr, tpe)) + case tree => + if (isIdempotentExpr(tree)) lit // See discussion in phase Literalize why we demand isIdempotentExpr + else Block(tree :: Nil, lit) + } + } + + def adaptToSubType(wtp: Type): Tree = { + // try converting a constant to the target type + val folded = ConstFold(tree, pt) + if (folded ne tree) return adaptConstant(folded, folded.tpe.asInstanceOf[ConstantType]) + // drop type if prototype is Unit + if (pt isRef defn.UnitClass) + // local adaptation makes sure every adapted tree conforms to its pt + // so will take the code path that decides on inlining + return tpd.Block(adapt(tree, WildcardType) :: Nil, Literal(Constant(()))) + // convert function literal to SAM closure + tree match { + case Closure(Nil, id @ Ident(nme.ANON_FUN), _) + if defn.isFunctionType(wtp) && !defn.isFunctionType(pt) => + pt match { + case SAMType(meth) + if wtp <:< meth.info.toFunctionType() => + // was ... && isFullyDefined(pt, ForceDegree.noBottom) + // but this prevents case blocks from implementing polymorphic partial functions, + // since we do not know the result parameter a priori. Have to wait until the + // body is typechecked. + return cpy.Closure(tree)(Nil, id, TypeTree(pt)).withType(pt) + case _ => + } + case _ => + } + // try an implicit conversion + inferView(tree, pt) match { + case SearchSuccess(inferred, _, _) => + adapt(inferred, pt) + case failure: SearchFailure => + if (pt.isInstanceOf[ProtoType] && !failure.isInstanceOf[AmbiguousImplicits]) tree + else err.typeMismatch(tree, pt, failure) + } + } + + def adaptType(tp: Type): Tree = { + val tree1 = + if ((pt eq AnyTypeConstructorProto) || tp.typeParamSymbols.isEmpty) tree + else tree.withType(tree.tpe.EtaExpand(tp.typeParamSymbols)) + if ((ctx.mode is Mode.Pattern) || tree1.tpe <:< pt) tree1 + else err.typeMismatch(tree1, pt) + } + + tree match { + case _: MemberDef | _: PackageDef | _: Import | _: WithoutTypeOrPos[_] => tree + case _ => tree.tpe.widen match { + case _: ErrorType => + tree + case ref: TermRef => + pt match { + case pt: FunProto + if pt.args.lengthCompare(1) > 0 && isUnary(ref) && ctx.canAutoTuple => + adaptInterpolated(tree, pt.tupled, original) + case _ => + adaptOverloaded(ref) + } + case poly: PolyType if !(ctx.mode is Mode.Type) => + if (pt.isInstanceOf[PolyProto]) tree + else { + var typeArgs = tree match { + case Select(qual, nme.CONSTRUCTOR) => qual.tpe.widenDealias.argTypesLo + case _ => Nil + } + if (typeArgs.isEmpty) typeArgs = constrained(poly, tree)._2 + convertNewGenericArray( + adaptInterpolated(tree.appliedToTypes(typeArgs), pt, original)) + } + case wtp => + pt match { + case pt: FunProto => + adaptToArgs(wtp, pt) + case pt: PolyProto => + tryInsertApplyOrImplicit(tree, pt) { + (_, _) => tree // error will be reported in typedTypeApply + } + case _ => + if (ctx.mode is Mode.Type) adaptType(tree.tpe) + else adaptNoArgs(wtp) + } + } + } + } +} diff --git a/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala new file mode 100644 index 000000000..d5dd5a024 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala @@ -0,0 +1,148 @@ +package dotty.tools.dotc +package typer + +import dotty.tools.dotc.ast.{ Trees, tpd } +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 + * in tree are variance correct. Does not recurse inside methods. + * The method should be invoked once for each Template. + */ +object VarianceChecker { + case class VarianceError(tvar: Symbol, required: Variance) + def check(tree: tpd.Tree)(implicit ctx: Context) = + new VarianceChecker()(ctx).Traverser.traverse(tree) +} + +class VarianceChecker()(implicit ctx: Context) { + import VarianceChecker._ + import tpd._ + + private object Validator extends TypeAccumulator[Option[VarianceError]] { + private var base: Symbol = _ + + /** Is no variance checking needed within definition of `base`? */ + def ignoreVarianceIn(base: Symbol): Boolean = ( + base.isTerm + || base.is(Package) + || base.is(Local) + ) + + /** The variance of a symbol occurrence of `tvar` seen at the level of the definition of `base`. + * The search proceeds from `base` to the owner of `tvar`. + * Initially the state is covariant, but it might change along the search. + */ + def relativeVariance(tvar: Symbol, base: Symbol, v: Variance = Covariant): Variance = /*ctx.traceIndented(i"relative variance of $tvar wrt $base, so far: $v")*/ { + if (base == tvar.owner) v + else if ((base is Param) && base.owner.isTerm) + relativeVariance(tvar, paramOuter(base.owner), flip(v)) + else if (ignoreVarianceIn(base.owner)) Bivariant + else if (base.isAliasType) relativeVariance(tvar, base.owner, Invariant) + else relativeVariance(tvar, base.owner, v) + } + + /** The next level to take into account when determining the + * relative variance with a method parameter as base. The method + * is always skipped. If the method is a constructor, we also skip + * its class owner, because constructors are not checked for variance + * relative to the type parameters of their own class. On the other + * hand constructors do count for checking the variance of type parameters + * of enclosing classes. I believe the Scala 2 rules are too lenient in + * that respect. + */ + private def paramOuter(meth: Symbol) = + if (meth.isConstructor) meth.owner.owner else meth.owner + + /** Check variance of abstract type `tvar` when referred from `base`. */ + private def checkVarianceOfSymbol(tvar: Symbol): Option[VarianceError] = { + val relative = relativeVariance(tvar, base) + if (relative == Bivariant || tvar.is(BaseTypeArg)) None + else { + val required = compose(relative, this.variance) + def tvar_s = s"$tvar (${varianceString(tvar.flags)} ${tvar.showLocated})" + def base_s = s"$base in ${base.owner}" + (if (base.owner.isClass) "" else " in " + base.owner.enclosingClass) + ctx.log(s"verifying $tvar_s is ${varianceString(required)} at $base_s") + ctx.log(s"relative variance: ${varianceString(relative)}") + ctx.log(s"current variance: ${this.variance}") + ctx.log(s"owner chain: ${base.ownersIterator.toList}") + if (tvar is required) None + else Some(VarianceError(tvar, required)) + } + } + + /** For PolyTypes, type parameters are skipped because they are defined + * explicitly (their TypeDefs will be passed here.) For MethodTypes, the + * same is true of the parameters (ValDefs). + */ + def apply(status: Option[VarianceError], tp: Type): Option[VarianceError] = ctx.traceIndented(s"variance checking $tp of $base at $variance", variances) { + if (status.isDefined) status + else tp match { + case tp: TypeRef => + val sym = tp.symbol + if (sym.variance != 0 && base.isContainedIn(sym.owner)) checkVarianceOfSymbol(sym) + else if (sym.isAliasType) this(status, sym.info.bounds.hi) + else foldOver(status, tp) + case tp: MethodType => + this(status, tp.resultType) // params will be checked in their TypeDef nodes. + case tp: PolyType => + this(status, tp.resultType) // params will be checked in their ValDef nodes. + case AnnotatedType(_, annot) if annot.symbol == defn.UncheckedVarianceAnnot => + status + //case tp: ClassInfo => + // ??? not clear what to do here yet. presumably, it's all checked at local typedefs + case _ => + foldOver(status, tp) + } + } + + def validateDefinition(base: Symbol): Option[VarianceError] = { + val saved = this.base + this.base = base + try apply(None, base.info) + finally this.base = saved + } + } + + private object Traverser extends TreeTraverser { + 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) { + 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 => + } + + override def traverse(tree: Tree)(implicit ctx: Context) = { + def sym = tree.symbol + // No variance check for private/protected[this] methods/values. + def skip = + !sym.exists || + sym.is(Local) || // !!! watch out for protected local! + sym.is(TypeParam) && sym.owner.isClass // already taken care of in primary constructor of class + tree match { + case defn: MemberDef if skip => + ctx.debuglog(s"Skipping variance check of ${sym.showDcl}") + case tree: TypeDef => + checkVariance(sym, tree.pos) + case tree: ValDef => + checkVariance(sym, tree.pos) + case DefDef(_, tparams, vparamss, _, _) => + checkVariance(sym, tree.pos) + tparams foreach traverse + vparamss foreach (_ foreach traverse) + case Template(_, _, _, body) => + traverseChildren(tree) + case _ => + } + } + } +} diff --git a/compiler/src/dotty/tools/dotc/typer/Variances.scala b/compiler/src/dotty/tools/dotc/typer/Variances.scala new file mode 100644 index 000000000..92bd9fd74 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/Variances.scala @@ -0,0 +1,116 @@ +package dotty.tools.dotc +package typer + +import dotty.tools.dotc.ast.{Trees, tpd} +import core._ +import Types._, Contexts._, Flags._, Symbols._, Annotations._, Trees._ +import Decorators._ + +object Variances { + import tpd._ + + type Variance = FlagSet + val Bivariant = VarianceFlags + val Invariant = EmptyFlags + + /** Flip between covariant and contravariant */ + def flip(v: Variance): Variance = { + if (v == Covariant) Contravariant + else if (v == Contravariant) Covariant + else v + } + + /** Map everything below Bivariant to Invariant */ + def cut(v: Variance): Variance = + if (v == Bivariant) v else Invariant + + def compose(v: Variance, boundsVariance: Int) = + if (boundsVariance == 1) v + else if (boundsVariance == -1) flip(v) + else cut(v) + + /** Compute variance of type parameter `tparam' in types of all symbols `sym'. */ + def varianceInSyms(syms: List[Symbol])(tparam: Symbol)(implicit ctx: Context): Variance = + (Bivariant /: syms) ((v, sym) => v & varianceInSym(sym)(tparam)) + + /** Compute variance of type parameter `tparam' in type of symbol `sym'. */ + def varianceInSym(sym: Symbol)(tparam: Symbol)(implicit ctx: Context): Variance = + if (sym.isAliasType) cut(varianceInType(sym.info)(tparam)) + else varianceInType(sym.info)(tparam) + + /** Compute variance of type parameter `tparam' in all types `tps'. */ + def varianceInTypes(tps: List[Type])(tparam: Symbol)(implicit ctx: Context): Variance = + (Bivariant /: tps) ((v, tp) => v & varianceInType(tp)(tparam)) + + /** Compute variance of type parameter `tparam' in all type arguments + * <code>tps</code> which correspond to formal type parameters `tparams1'. + */ + def varianceInArgs(tps: List[Type], tparams1: List[Symbol])(tparam: Symbol)(implicit ctx: Context): Variance = { + var v: Variance = Bivariant; + for ((tp, tparam1) <- tps zip tparams1) { + val v1 = varianceInType(tp)(tparam) + v = v & (if (tparam1.is(Covariant)) v1 + else if (tparam1.is(Contravariant)) flip(v1) + else cut(v1)) + } + v + } + + /** Compute variance of type parameter `tparam' in all type annotations `annots'. */ + def varianceInAnnots(annots: List[Annotation])(tparam: Symbol)(implicit ctx: Context): Variance = { + (Bivariant /: annots) ((v, annot) => v & varianceInAnnot(annot)(tparam)) + } + + /** Compute variance of type parameter `tparam' in type annotation `annot'. */ + def varianceInAnnot(annot: Annotation)(tparam: Symbol)(implicit ctx: Context): Variance = { + varianceInType(annot.tree.tpe)(tparam) + } + + /** Compute variance of type parameter <code>tparam</code> in type <code>tp</code>. */ + def varianceInType(tp: Type)(tparam: Symbol)(implicit ctx: Context): Variance = tp match { + case TermRef(pre, _) => + varianceInType(pre)(tparam) + case tp @ TypeRef(pre, _) => + if (tp.symbol == tparam) Covariant else varianceInType(pre)(tparam) + case tp @ TypeBounds(lo, hi) => + if (lo eq hi) compose(varianceInType(hi)(tparam), tp.variance) + else flip(varianceInType(lo)(tparam)) & varianceInType(hi)(tparam) + case tp @ RefinedType(parent, _, rinfo) => + varianceInType(parent)(tparam) & varianceInType(rinfo)(tparam) + case tp: RecType => + varianceInType(tp.parent)(tparam) + case tp @ MethodType(_, paramTypes) => + flip(varianceInTypes(paramTypes)(tparam)) & varianceInType(tp.resultType)(tparam) + case ExprType(restpe) => + varianceInType(restpe)(tparam) + case tp @ HKApply(tycon, args) => + def varianceInArgs(v: Variance, args: List[Type], tparams: List[TypeParamInfo]): Variance = + args match { + case arg :: args1 => + varianceInArgs( + v & compose(varianceInType(arg)(tparam), tparams.head.paramVariance), + args1, tparams.tail) + case nil => + v + } + varianceInArgs(varianceInType(tycon)(tparam), args, tycon.typeParams) + case tp: PolyType => + flip(varianceInTypes(tp.paramBounds)(tparam)) & varianceInType(tp.resultType)(tparam) + case AnnotatedType(tp, annot) => + varianceInType(tp)(tparam) & varianceInAnnot(annot)(tparam) + case tp: AndOrType => + varianceInType(tp.tp1)(tparam) & varianceInType(tp.tp2)(tparam) + case _ => + Bivariant + } + + def varianceString(v: Variance) = + if (v is Covariant) "covariant" + else if (v is Contravariant) "contravariant" + else "invariant" + + def varianceString(v: Int) = + if (v > 0) "+" + else if (v < 0) "-" + else "" +} |