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: GenericType)(implicit ctx: Context): Unit = checkBounds(args, poly.paramBounds, _.substParams(poly, _)) /** 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 checkWildcardHKApply(tp: Type, pos: Position)(implicit ctx: Context): Unit = tp match { case tp @ HKApply(tycon, args) if args.exists(_.isInstanceOf[TypeBounds]) => tycon match { case tycon: TypeLambda => ctx.errorOrMigrationWarning( ex"unreducible application of higher-kinded type $tycon to wildcard arguments", pos) case _ => checkWildcardHKApply(tp.superType, pos) } case _ => } /** Traverse type tree, performing the following checks: * 1. All arguments of applied type trees must conform to their bounds. * 2. Prefixes of type selections and singleton types must be realizable. */ val typeChecker = new TreeTraverser { def traverse(tree: Tree)(implicit ctx: Context) = { tree match { case AppliedTypeTree(tycon, args) => // 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 checkValidIfHKApply(implicit ctx: Context): Unit = checkWildcardHKApply(tycon.tpe.appliedTo(args.map(_.tpe)), tree.pos) checkValidIfHKApply(ctx.addMode(Mode.AllowLambdaWildcardApply)) case Select(qual, name) if name.isTypeName => checkRealizable(qual.tpe, qual.pos.focus) case SingletonTypeTree(ref) => checkRealizable(ref.tpe, ref.pos.focus) case _ => } traverseChildren(tree) } } /** 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.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 } 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 }