From a468a864dcdb89091985c194737968a979e874fb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 21 Nov 2014 18:09:10 +0100 Subject: checkBounds refactoring Move core logic to TypeOps, only leave error reporting in Checking. That way, we have the option of re-using the code as a simple test elsewhere. --- src/dotty/tools/dotc/core/TypeOps.scala | 36 ++++++++++++++++++++++ .../tools/dotc/transform/FirstTransform.scala | 7 +---- src/dotty/tools/dotc/typer/Applications.scala | 2 +- src/dotty/tools/dotc/typer/Checking.scala | 29 ++++------------- 4 files changed, 44 insertions(+), 30 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 687ca9ef0..7b76feb4d 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -7,6 +7,8 @@ import config.Printers._ import Decorators._ import StdNames._ import util.SimpleMap +import collection.mutable +import ast.tpd._ trait TypeOps { this: Context => @@ -307,6 +309,40 @@ trait TypeOps { this: Context => parentRefs } + /** An argument bounds violation is a triple consisting of + * - the argument tree + * - a string "upper" or "lower" indicating which bound is violated + * - the violated bound + */ + type BoundsViolation = (Tree, String, Type) + + /** The list of violations where arguments are not within bounds. + * @param args The arguments + * @param boundss The list of type bounds + * @param instantiate A function that maps a bound type and the list of argument types to a resulting type. + * Needed to handle bounds that refer to other bounds. + */ + def boundsViolations(args: List[Tree], boundss: List[TypeBounds], instantiate: (Type, List[Type]) => Type)(implicit ctx: Context): List[BoundsViolation] = { + val argTypes = args.tpes + val violations = new mutable.ListBuffer[BoundsViolation] + for ((arg, bounds) <- args zip boundss) { + def checkOverlapsBounds(lo: Type, hi: Type): Unit = { + //println(i"instantiating ${bounds.hi} with $argTypes") + //println(i" = ${instantiate(bounds.hi, argTypes)}") + val hiBound = instantiate(bounds.hi, argTypes.mapConserve(_.bounds.hi)) + // Note that argTypes can contain a TypeBounds type for arguments that are + // not fully determined. In that case we need to check against the hi bound of the argument. + if (!(lo <:< hiBound)) violations += ((arg, "upper", hiBound)) + if (!(bounds.lo <:< hi)) violations += ((arg, "lower", bounds.lo)) + } + arg.tpe match { + case TypeBounds(lo, hi) => checkOverlapsBounds(lo, hi) + case tp => checkOverlapsBounds(tp, tp) + } + } + violations.toList + } + /** Is `feature` enabled in class `owner`? * This is the case if one of the following two alternatives holds: * diff --git a/src/dotty/tools/dotc/transform/FirstTransform.scala b/src/dotty/tools/dotc/transform/FirstTransform.scala index a58e8a643..42ace148a 100644 --- a/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -141,12 +141,7 @@ class FirstTransform extends MiniPhaseTransform with IdentityDenotTransformer wi val tparams = tycon.tpe.typeSymbol.typeParams val bounds = tparams.map(tparam => tparam.info.asSeenFrom(tycon.tpe.normalizedPrefix, tparam.owner.owner).bounds) - def instantiateUpperBound(tp: Type, argTypes: List[Type]): Type = { - tp.substDealias(tparams, argTypes).bounds.hi - // not that argTypes can contain a TypeBounds type for arguments that are - // not fully determined. In that case we need to check against the hi bound. - } - Checking.checkBounds(args, bounds, instantiateUpperBound) + Checking.checkBounds(args, bounds, _.substDealias(tparams, _)) normalizeType(tree) case tree => normalizeType(tree) diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 9b2e64f35..ba770cf2c 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -593,7 +593,7 @@ trait Applications extends Compatibility { self: Typer => else tree if (typedArgs.length <= pt.paramBounds.length) typedArgs = typedArgs.zipWithConserve(pt.paramBounds)(adaptTypeArg) - checkBounds(typedArgs, pt, tree.pos) + checkBounds(typedArgs, pt) case _ => } assignType(cpy.TypeApply(tree)(typedFn, typedArgs), typedFn, typedArgs) diff --git a/src/dotty/tools/dotc/typer/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala index fdc70e207..2ff2e9e3b 100644 --- a/src/dotty/tools/dotc/typer/Checking.scala +++ b/src/dotty/tools/dotc/typer/Checking.scala @@ -33,27 +33,11 @@ object Checking { /** A general checkBounds method that can be used for TypeApply nodes as * well as for AppliedTypeTree nodes. */ - def checkBounds(args: List[tpd.Tree], boundss: List[TypeBounds], instantiate: (Type, List[Type]) => Type)(implicit ctx: Context) = { - val argTypes = args.tpes - for ((arg, bounds) <- args zip boundss) { - def notConforms(which: String, bound: Type) = { - ctx.error( + def checkBounds(args: List[tpd.Tree], boundss: List[TypeBounds], instantiate: (Type, List[Type]) => Type)(implicit ctx: Context) = + for ((arg, which, bound) <- ctx.boundsViolations(args, boundss, instantiate)) + ctx.error( d"Type argument ${arg.tpe} does not conform to $which bound $bound ${err.whyNoMatchStr(arg.tpe, bound)}", arg.pos) - } - def checkOverlapsBounds(lo: Type, hi: Type): Unit = { - //println(i"instantiating ${bounds.hi} with $argTypes") - //println(i" = ${instantiate(bounds.hi, argTypes)}") - val hiBound = instantiate(bounds.hi, argTypes) - if (!(lo <:< hiBound)) notConforms("upper", hiBound) - if (!(bounds.lo <:< hi)) notConforms("lower", bounds.lo) - } - arg.tpe match { - case TypeBounds(lo, hi) => checkOverlapsBounds(lo, hi) - case tp => checkOverlapsBounds(tp, tp) - } - } - } /** A type map which checks that the only cycles in a type are F-bounds * and that protects all F-bounded references by LazyRefs. @@ -192,10 +176,9 @@ trait Checking { /** 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 - * TODO: remove pos parameter */ - def checkBounds(args: List[tpd.Tree], poly: PolyType, pos: Position)(implicit ctx: Context): Unit = Checking.checkBounds( - args, poly.paramBounds, (tp, argTypes) => tp.substParams(poly, argTypes)) + def checkBounds(args: List[tpd.Tree], poly: PolyType)(implicit ctx: Context): Unit = + Checking.checkBounds(args, poly.paramBounds, _.substParams(poly, _)) /** Check that type `tp` is stable. */ def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit = @@ -292,7 +275,7 @@ 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 checkBounds(args: List[tpd.Tree], poly: PolyType, pos: Position)(implicit ctx: Context): Unit = () + override def checkBounds(args: List[tpd.Tree], poly: PolyType)(implicit ctx: Context): Unit = () override def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit = () override def checkLegalPrefix(tp: Type, selector: Name, pos: Position)(implicit ctx: Context): Unit = () override def checkClassTypeWithStablePrefix(tp: Type, pos: Position, traitReq: Boolean)(implicit ctx: Context): Type = tp -- cgit v1.2.3