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.filter( 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(_.paramInfo) 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 avoidPrivateLeaks(sym: Symbol, pos: Position)(implicit ctx: Context): Type = if (!sym.is(SyntheticOrPrivate) && sym.owner.isClass) Checking.checkNoPrivateLeaks(sym, pos) else sym.info 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) tpe else errorType(ex"$what cannot be accessed as a member of $pre$where.$whyNot", pos) } } else if (d.symbol is TypeParamAccessor) ensureAccessible(d.info.bounds.hi, superAccess, pos) 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) UnspecifiedErrorType else { def kind = if (name.isTypeName) "type" else "value" def addendum = if (site.derivesFrom(defn.DynamicClass)) "\npossible cause: maybe a wrong Dynamic method signature?" else "" errorType( if (name == nme.CONSTRUCTOR) ex"$site does not have a constructor" else NotAMember(site, name, kind), pos) } } } /** 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( if (cls.isClass) cls.thisType else errorType("not a legal qualifying class for this", tree.pos)) } def assignType(tree: untpd.Super, qual: Tree, inConstrCall: Boolean, mixinClass: Symbol = NoSymbol)(implicit ctx: Context) = { val mix = tree.mix qual.tpe match { case err: ErrorType => untpd.cpy.Super(tree)(qual, mix).withType(err) case qtype @ ThisType(_) => val cls = qtype.cls def findMixinSuper(site: Type): Type = site.parents filter (_.name == mix.name) match { case p :: Nil => p case Nil => errorType(SuperQualMustBeParent(mix, 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)) } } /** Substitute argument type `argType` for parameter `pref` in type `tp`, * skolemizing the argument type if it is not stable and `pref` occurs in `tp`. */ def safeSubstParam(tp: Type, pref: ParamRef, argType: Type)(implicit ctx: Context) = { val tp1 = tp.substParam(pref, argType) if ((tp1 eq tp) || argType.isStable) tp1 else tp.substParam(pref, SkolemType(argType.widen)) } def assignType(tree: untpd.Apply, fn: Tree, args: List[Tree])(implicit ctx: Context) = { val ownType = fn.tpe.widen match { case fntpe: MethodType => def safeSubstParams(tp: Type, params: List[ParamRef], args: List[Tree]): Type = params match { case param :: params1 => val tp1 = safeSubstParam(tp, param, args.head.tpe) safeSubstParams(tp1, params1, args.tail) case Nil => tp } if (sameLength(fntpe.paramInfos, args) || ctx.phase.prev.relaxedTyping) if (fntpe.isDependent) safeSubstParams(fntpe.resultType, fntpe.paramRefs, args) else fntpe.resultType else errorType(i"wrong number of arguments for $fntpe: ${fn.tpe}, expected: ${fntpe.paramInfos.length}, found: ${args.length}", 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: TypeLambda => 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 TypeParamRef(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 TypeParamRef(`pt`, idx) => normArgs(idx) case _ => mapOver(t) } } val resultType1 = transform(pt.resultType) if (gapBuf.isEmpty) resultType1 else { val gaps = gapBuf.toList pt.derivedLambdaType( gaps.map(paramNames), gaps.map(idx => transform(pt.paramInfos(idx)).bounds), resultType1) } } else { val argTypes = args.tpes if (sameLength(argTypes, paramNames) || ctx.phase.prev.relaxedTyping) pt.instantiate(argTypes) else wrongNumberOfTypeArgs(fn.tpe, 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) = { assert(!hasNamedArg(args)) val tparams = tycon.tpe.typeParams val ownType = if (sameLength(tparams, args)) tycon.tpe.appliedTo(args.tpes) else wrongNumberOfTypeArgs(tycon.tpe, tparams, args, tree.pos) tree.withType(ownType) } def assignType(tree: untpd.LambdaTypeTree, tparamDefs: List[TypeDef], body: Tree)(implicit ctx: Context) = tree.withType(HKTypeLambda.fromParams(tparamDefs.map(_.symbol.asType), body.tpe)) 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