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