package dotty.tools package dotc package ast import core._ import util.Positions._, Types._, Contexts._, Constants._, Names._, Flags._ import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._ // TODO: revise, integrate in a checking phase. object CheckTrees { import tpd._ def check(p: Boolean, msg: => String = "")(implicit ctx: Context): Unit = assert(p, msg) def checkTypeArg(arg: Tree, bounds: TypeBounds)(implicit ctx: Context): Unit = { check(arg.isValueType) check(bounds contains arg.tpe) } def escapingRefs(block: Block)(implicit ctx: Context): collection.Set[NamedType] = { var hoisted: Set[Symbol] = Set() lazy val locals = ctx.typeAssigner.localSyms(block.stats).toSet def isLocal(sym: Symbol): Boolean = (locals contains sym) && !isHoistableClass(sym) def isHoistableClass(sym: Symbol) = sym.isClass && { (hoisted contains sym) || { hoisted += sym !classLeaks(sym.asClass) } } def leakingTypes(tp: Type): collection.Set[NamedType] = tp namedPartsWith (tp => isLocal(tp.symbol)) def typeLeaks(tp: Type): Boolean = leakingTypes(tp).nonEmpty def classLeaks(sym: ClassSymbol): Boolean = (ctx.owner is Method) || // can't hoist classes out of method bodies (sym.info.parents exists typeLeaks) || (sym.decls.toList exists (t => typeLeaks(t.info))) leakingTypes(block.tpe) } def checkType(tree: Tree)(implicit ctx: Context): Unit = tree match { case Ident(name) => case Select(qualifier, name) => check(qualifier.isValue) check(qualifier.tpe =:= tree.tpe.normalizedPrefix) val denot = qualifier.tpe.member(name) check(denot.exists) check(denot.hasAltWith(_.symbol == tree.symbol)) case This(cls) => case Super(qual, mixin) => check(qual.isValue) val cls = qual.tpe.typeSymbol check(cls.isClass) case Apply(fn, args) => def checkArg(arg: Tree, name: Name, formal: Type): Unit = { arg match { case NamedArg(argName, _) => check(argName == name) case _ => check(arg.isValue) } check(arg.tpe <:< formal) } val MethodType(paramNames, paramTypes) = fn.tpe.widen // checked already at construction (args, paramNames, paramTypes).zipped foreach checkArg case TypeApply(fn, args) => val pt @ PolyType(_) = fn.tpe.widen // checked already at construction (args, pt.instantiateBounds(args map (_.tpe))).zipped foreach checkTypeArg case Literal(const: Constant) => case New(tpt) => check(tpt.isValueType) val cls = tpt.tpe.typeSymbol check(cls.isClass) check(!(cls is AbstractOrTrait)) case Pair(left, right) => check(left.isValue) check(right.isValue) case Typed(expr, tpt) => check(tpt.isValueType) expr.tpe.widen match { case tp: MethodType => val cls = tpt.tpe.typeSymbol check(cls.isClass) check((cls is Trait) || cls.primaryConstructor.info.paramTypess.flatten.isEmpty) val absMembers = tpt.tpe.abstractTermMembers check(absMembers.size == 1) check(tp <:< absMembers.head.info) case _ => check(expr.isValueOrPattern) check(expr.tpe <:< tpt.tpe.translateParameterized(defn.RepeatedParamClass, defn.SeqClass)) } case NamedArg(name, arg) => case Assign(lhs, rhs) => check(lhs.isValue); check(rhs.isValue) lhs.tpe match { case ltpe: TermRef => check(ltpe.symbol is Mutable) case _ => check(false) } check(rhs.tpe <:< lhs.tpe.widen) case tree @ Block(stats, expr) => check(expr.isValue) check(escapingRefs(tree).isEmpty) case If(cond, thenp, elsep) => check(cond.isValue); check(thenp.isValue); check(elsep.isValue) check(cond.tpe isRef defn.BooleanClass) case Closure(env, meth, target) => meth.tpe.widen match { case mt @ MethodType(_, paramTypes) => if (target.isEmpty) { check(env.length < paramTypes.length) for ((arg, formal) <- env zip paramTypes) check(arg.tpe <:< formal) } else // env is stored in class, not method target.tpe match { case SAMType(targetMeth) => check(mt <:< targetMeth.info) } } case Match(selector, cases) => check(selector.isValue) // are any checks that relate selector and patterns desirable? case CaseDef(pat, guard, body) => check(pat.isValueOrPattern); check(guard.isValue); check(body.isValue) check(guard.tpe.derivesFrom(defn.BooleanClass)) case Return(expr, from) => check(expr.isValue); check(from.isTerm) check(from.tpe.termSymbol.isRealMethod) case Try(block, handler, finalizer) => check(block.isTerm) check(finalizer.isTerm) check(handler.isTerm) check(handler.tpe derivesFrom defn.FunctionClass(1)) check(handler.tpe.baseArgInfos(defn.FunctionClass(1)).head <:< defn.ThrowableType) case Throw(expr) => check(expr.isValue) check(expr.tpe.derivesFrom(defn.ThrowableClass)) case SeqLiteral(elems) => val elemtp = tree.tpe.elemType for (elem <- elems) { check(elem.isValue) check(elem.tpe <:< elemtp) } case TypeTree(original) => if (!original.isEmpty) { check(original.isValueType) check(original.tpe == tree.tpe) } case SingletonTypeTree(ref) => check(ref.isValue) check(ref.symbol.isStable) case SelectFromTypeTree(qualifier, name) => check(qualifier.isValueType) check(qualifier.tpe =:= tree.tpe.normalizedPrefix) val denot = qualifier.tpe.member(name) check(denot.exists) check(denot.symbol == tree.symbol) case AndTypeTree(left, right) => check(left.isValueType); check(right.isValueType) case OrTypeTree(left, right) => check(left.isValueType); check(right.isValueType) case RefinedTypeTree(tpt, refinements) => check(tpt.isValueType) def checkRefinements(forbidden: Set[Symbol], rs: List[Tree]): Unit = rs match { case r :: rs1 => val rsym = r.symbol check(rsym.isTerm || rsym.isAbstractOrAliasType) if (rsym.isAbstractType) check(tpt.tpe.member(rsym.name).exists) check(rsym.info forallParts { case nt: NamedType => !(forbidden contains nt.symbol) case _ => true }) checkRefinements(forbidden - rsym, rs1) case nil => } checkRefinements(ctx.typeAssigner.localSyms(refinements).toSet, refinements) case AppliedTypeTree(tpt, args) => check(tpt.isValueType) val tparams = tpt.tpe.typeParams check(sameLength(tparams, args)) (args, tparams map (_.info.bounds)).zipped foreach checkTypeArg case TypeBoundsTree(lo, hi) => check(lo.isValueType); check(hi.isValueType) check(lo.tpe <:< hi.tpe) case Bind(sym, body) => check(body.isValueOrPattern) check(!(tree.symbol is Method)) body match { case Ident(nme.WILDCARD) => case _ => check(body.tpe.widen =:= tree.symbol.info) } case Alternative(alts) => for (alt <- alts) check(alt.isValueOrPattern) case UnApply(fun, implicits, args) => // todo: review check(fun.isTerm) for (arg <- args) check(arg.isValueOrPattern) val funtpe @ MethodType(_, _) = fun.tpe.widen fun.symbol.name match { // check arg arity case nme.unapplySeq => // args need to be wrapped in (...: _*) check(args.length == 1) check(args.head.isInstanceOf[SeqLiteral]) case nme.unapply => val rtp = funtpe.resultType if (rtp isRef defn.BooleanClass) check(args.isEmpty) else { check(rtp isRef defn.OptionClass) val normArgs = rtp.argTypesHi match { case optionArg :: Nil => optionArg.argTypesHi match { case Nil => optionArg :: Nil case tupleArgs if defn.isTupleType(optionArg) => tupleArgs } case _ => check(false) Nil } check(sameLength(normArgs, args)) } } case ValDef(mods, name, tpt, rhs) => check(!(tree.symbol is Method)) if (!rhs.isEmpty) { check(rhs.isValue) check(rhs.tpe <:< tpt.tpe) } case DefDef(mods, name, tparams, vparamss, tpt, rhs) => check(tree.symbol is Method) if (!rhs.isEmpty) { check(rhs.isValue) check(rhs.tpe <:< tpt.tpe) } case TypeDef(mods, name, tpt) => check(tpt.isInstanceOf[Template] || tpt.tpe.isInstanceOf[TypeBounds]) case Template(constr, parents, selfType, body) => case Import(expr, selectors) => check(expr.isValue) check(expr.tpe.termSymbol.isStable) case PackageDef(pid, stats) => check(pid.isTerm) check(pid.symbol is Package) case Annotated(annot, arg) => check(annot.isInstantiation) check(annot.symbol.owner.isSubClass(defn.AnnotationClass)) check(arg.isValueType || arg.isValue) case EmptyTree => } }