diff options
author | odersky <odersky@gmail.com> | 2016-12-18 15:44:03 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-12-18 15:44:03 +0100 |
commit | 18b8daa30916b2efaa59638c21198d401a67e46a (patch) | |
tree | 09c1a3be43132c89f84605f78429d0094e8749e6 | |
parent | 5df985cc599a0c69029773e3416bbb5fc883476a (diff) | |
parent | 1f9990b58c23ba74a8d96166700e755704b78a49 (diff) | |
download | dotty-18b8daa30916b2efaa59638c21198d401a67e46a.tar.gz dotty-18b8daa30916b2efaa59638c21198d401a67e46a.tar.bz2 dotty-18b8daa30916b2efaa59638c21198d401a67e46a.zip |
Merge pull request #1817 from dotty-staging/fix-#1802
Fix #1802: Make sure errors are not swept under the carpet
19 files changed, 102 insertions, 55 deletions
diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index ed32d2df9..123dc3450 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -1024,7 +1024,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma case JavaArrayType(elem) => elem case _ => ctx.error(s"JavaSeqArray with type ${field.tpe} reached backend: $field", field.pos) - ErrorType + UnspecifiedErrorType } def _2: List[Tree] = field.elems } diff --git a/compiler/src/dotty/tools/backend/jvm/scalaPrimitives.scala b/compiler/src/dotty/tools/backend/jvm/scalaPrimitives.scala index eb4da5400..89831e56b 100644 --- a/compiler/src/dotty/tools/backend/jvm/scalaPrimitives.scala +++ b/compiler/src/dotty/tools/backend/jvm/scalaPrimitives.scala @@ -12,7 +12,7 @@ import dotty.tools.dotc.ast.tpd._ import dotty.tools.dotc.core.Names.TermName import dotty.tools.dotc.core.StdNames import dotty.tools.dotc.core.StdNames._ -import dotty.tools.dotc.core.Types.{JavaArrayType, ErrorType, Type} +import dotty.tools.dotc.core.Types.{JavaArrayType, UnspecifiedErrorType, Type} import scala.collection.{ mutable, immutable } @@ -73,7 +73,7 @@ class DottyPrimitives(ctx: Context) { case JavaArrayType(el) => el case _ => ctx.error(s"expected Array $tpe") - ErrorType + UnspecifiedErrorType } code match { diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 20273eb85..28ae89c4a 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -113,7 +113,7 @@ object Trees { * type. (Overridden by empty trees) */ def withType(tpe: Type)(implicit ctx: Context): ThisTree[Type] = { - if (tpe == ErrorType) assert(ctx.reporter.errorsReported) + if (tpe.isInstanceOf[ErrorType]) assert(ctx.reporter.errorsReported) withTypeUnchecked(tpe) } diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index e2bb0ac1a..aaae78c57 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1937,12 +1937,12 @@ object SymDenotations { /** A completer for missing references */ class StubInfo() extends LazyType { - def initializeToDefaults(denot: SymDenotation)(implicit ctx: Context) = { + def initializeToDefaults(denot: SymDenotation, errMsg: => String)(implicit ctx: Context) = { denot.info = denot match { case denot: ClassDenotation => ClassInfo(denot.owner.thisType, denot.classSymbol, Nil, EmptyScope) case _ => - ErrorType + new ErrorType(errMsg) } denot.privateWithin = NoSymbol } @@ -1954,13 +1954,14 @@ object SymDenotations { if (file != null) (s" in $file", file.toString) else ("", "the signature") val name = ctx.fresh.setSetting(ctx.settings.debugNames, true).nameString(denot.name) - ctx.error( + def errMsg = i"""bad symbolic reference. A signature$location |refers to $name in ${denot.owner.showKind} ${denot.owner.showFullName} which is not available. |It may be completely missing from the current classpath, or the version on - |the classpath might be incompatible with the version used when compiling $src.""") + |the classpath might be incompatible with the version used when compiling $src.""" + ctx.error(errMsg) if (ctx.debug) throw new Error() - initializeToDefaults(denot) + initializeToDefaults(denot, errMsg) } } diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index cfd85c49c..b10b94ad8 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -22,6 +22,7 @@ import NameOps._ import ast.tpd.Tree import ast.TreeTypeMap import Constants.Constant +import reporting.diagnostic.Message import Denotations.{ Denotation, SingleDenotation, MultiDenotation } import collection.mutable import io.AbstractFile @@ -290,9 +291,11 @@ trait Symbols { this: Context => */ def newSkolem(tp: Type) = newSymbol(defn.RootClass, nme.SKOLEM, SyntheticArtifact | Permanent, tp) - def newErrorSymbol(owner: Symbol, name: Name) = + def newErrorSymbol(owner: Symbol, name: Name, msg: => Message) = { + val errType = new ErrorType(msg) newSymbol(owner, name, SyntheticArtifact, - if (name.isTypeName) TypeAlias(ErrorType) else ErrorType) + if (name.isTypeName) TypeAlias(errType) else errType) + } /** Map given symbols, subjecting their attributes to the mappings * defined in the given TreeTypeMap `ttmap`. diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 743220f55..e8905320b 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -265,7 +265,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case ConstantType(v1) => v1.value == v2.value case _ => secondTry(tp1, tp2) } - case ErrorType => + case _: FlexType => true case _ => secondTry(tp1, tp2) @@ -341,7 +341,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { false } joinOK || isSubType(tp11, tp2) && isSubType(tp12, tp2) - case ErrorType => + case _: FlexType => true case _ => thirdTry(tp1, tp2) diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 82943377a..2e7c1db04 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -55,7 +55,7 @@ object TypeErasure { tp.paramTypes.forall(isErasedType) && isErasedType(tp.resultType) case tp @ ClassInfo(pre, _, parents, decls, _) => isErasedType(pre) && parents.forall(isErasedType) //&& decls.forall(sym => isErasedType(sym.info)) && isErasedType(tp.selfType) - case NoType | NoPrefix | WildcardType | ErrorType | SuperType(_, _) => + case NoType | NoPrefix | WildcardType | _: ErrorType | SuperType(_, _) => true case _ => false @@ -398,7 +398,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean tp.derivedClassInfo(NoPrefix, parents, erasedDecls, erasedRef(tp.selfType)) // can't replace selftype by NoType because this would lose the sourceModule link } - case NoType | NoPrefix | ErrorType | JavaArrayType(_) => + case NoType | NoPrefix | _: ErrorType | JavaArrayType(_) => tp case tp: WildcardType if wildcardOK => tp @@ -506,7 +506,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean if (inst.exists) sigName(inst) else tpnme.Uninstantiated case tp: TypeProxy => sigName(tp.underlying) - case ErrorType | WildcardType => + case _: ErrorType | WildcardType => tpnme.WILDCARD case tp: WildcardType => sigName(tp.optBounds) diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 39214dd0c..f134412a7 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -433,7 +433,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. formals = formals.updated(name, tp1.typeParamNamed(name)) } normalizeToRef(tp1) - case ErrorType => + case _: ErrorType => defn.AnyType case AnnotatedType(tpe, _) => normalizeToRef(tpe) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 4dffc4542..9884cc70a 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -15,9 +15,10 @@ import SymDenotations._ import Decorators._ import Denotations._ import Periods._ -import util.Positions.Position +import util.Positions.{Position, NoPosition} import util.Stats._ import util.{DotClass, SimpleMap} +import reporting.diagnostic.Message import ast.tpd._ import ast.TreeTypeMap import printing.Texts._ @@ -176,7 +177,7 @@ object Types { /** Is this type produced as a repair for an error? */ final def isError(implicit ctx: Context): Boolean = stripTypeVar match { - case ErrorType => true + case _: ErrorType => true case tp => (tp.typeSymbol is Erroneous) || (tp.termSymbol is Erroneous) } @@ -387,8 +388,8 @@ object Types { tp.decls.denotsNamed(name).filterExcluded(excluded).toDenot(NoPrefix) case tp: TypeProxy => tp.underlying.findDecl(name, excluded) - case ErrorType => - ctx.newErrorSymbol(classSymbol orElse defn.RootClass, name) + case err: ErrorType => + ctx.newErrorSymbol(classSymbol orElse defn.RootClass, name, err.msg) case _ => NoDenotation } @@ -453,8 +454,8 @@ object Types { go(tp.join) case tp: JavaArrayType => defn.ObjectType.findMember(name, pre, excluded) - case ErrorType => - ctx.newErrorSymbol(pre.classSymbol orElse defn.RootClass, name) + case err: ErrorType => + ctx.newErrorSymbol(pre.classSymbol orElse defn.RootClass, name, err.msg) case _ => NoDenotation } @@ -1497,7 +1498,7 @@ object Types { (lastDefRunId != sym.defRunId) || (lastDefRunId == NoRunId) } || - (lastSymbol.infoOrCompleter == ErrorType || + (lastSymbol.infoOrCompleter.isInstanceOf[ErrorType] || sym.owner != lastSymbol.owner && (sym.owner.derivesFrom(lastSymbol.owner) || selfTypeOf(sym).derivesFrom(lastSymbol.owner) || @@ -2693,7 +2694,7 @@ object Types { protected def checkInst(implicit ctx: Context): this.type = { def check(tycon: Type): Unit = tycon.stripTypeVar match { case tycon: TypeRef if !tycon.symbol.isClass => - case _: PolyParam | ErrorType | _: WildcardType => + case _: PolyParam | _: ErrorType | _: WildcardType => case _: PolyType => assert(args.exists(_.isInstanceOf[TypeBounds]), s"unreduced type apply: $this") case tycon: AnnotatedType => @@ -3251,12 +3252,19 @@ object Types { override def computeHash = hashSeed } - abstract class ErrorType extends UncachedGroundType with ValueType + /** A common superclass of `ErrorType` and `TryDynamicCallSite`. Instances of this + * class are at the same time subtypes and supertypes of every other type. + */ + abstract class FlexType extends UncachedGroundType with ValueType + + class ErrorType(_msg: => Message) extends FlexType { + val msg = _msg + } - object ErrorType extends ErrorType + object UnspecifiedErrorType extends ErrorType("unspecified error") /* Type used to track Select nodes that could not resolve a member and their qualifier is a scala.Dynamic. */ - object TryDynamicCallType extends ErrorType + object TryDynamicCallType extends FlexType /** Wildcard type, possibly with bounds */ abstract case class WildcardType(optBounds: Type) extends CachedGroundType with TermType { diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala index 0f63b25bb..92ab10db9 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -90,7 +90,8 @@ object JavaParsers { if (skipIt) skip() } - def errorTypeTree = TypeTree().withType(ErrorType) withPos Position(in.offset) + + def errorTypeTree = TypeTree().withType(UnspecifiedErrorType) withPos Position(in.offset) // --------- tree building ----------------------------- diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala index b321d3736..05f1af9d7 100644 --- a/compiler/src/dotty/tools/dotc/printing/Formatting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -96,7 +96,10 @@ object Formatting { case tpe: Type => tpe.exists && !tpe.isErroneous case sym: Symbol if sym.isCompleted => - sym.info != ErrorType && sym.info != TypeAlias(ErrorType) && sym.info.exists + sym.info match { + case _: ErrorType | TypeAlias(_: ErrorType) | NoType => false + case _ => true + } case _ => true } diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 15c382bb0..61f23c214 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -154,7 +154,7 @@ class PlainPrinter(_ctx: Context) extends Printer { changePrec(AndPrec) { toText(tp1) ~ " & " ~ toText(tp2) } case OrType(tp1, tp2) => changePrec(OrPrec) { toText(tp1) ~ " | " ~ toText(tp2) } - case ErrorType => + case _: ErrorType => "<error>" case tp: WildcardType => if (tp.optBounds.exists) "(?" ~ toTextRHS(tp.bounds) ~ ")" else "?" diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 4203ab9b2..da0a59c7b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -561,7 +561,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => var typedArgs = typedArgBuf.toList def app0 = cpy.Apply(app)(normalizedFun, typedArgs) // needs to be a `def` because typedArgs can change later val app1 = - if (!success) app0.withType(ErrorType) + if (!success) app0.withType(UnspecifiedErrorType) else { if (!sameSeq(args, orderedArgs)) { // need to lift arguments to maintain evaluation order in the @@ -654,7 +654,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => } fun1.tpe match { - case ErrorType => untpd.cpy.Apply(tree)(fun1, tree.args).withType(ErrorType) + case err: ErrorType => untpd.cpy.Apply(tree)(fun1, tree.args).withType(err) case TryDynamicCallType => typedDynamicApply(tree, pt) case _ => tryEither { @@ -918,7 +918,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => case tp => val unapplyErr = if (tp.isError) unapplyFn else notAnExtractor(unapplyFn) val typedArgsErr = args mapconserve (typed(_, defn.AnyType)) - cpy.UnApply(tree)(unapplyErr, Nil, typedArgsErr) withType ErrorType + cpy.UnApply(tree)(unapplyErr, Nil, typedArgsErr) withType unapplyErr.tpe } } diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index f5f7bdbaa..41d9f9572 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -56,7 +56,7 @@ object Checking { def checkBounds(args: List[tpd.Tree], poly: PolyType)(implicit ctx: Context): Unit = checkBounds(args, poly.paramBounds, _.substParams(poly, _)) - /** Check applied type trees for well-formedness. This means + /** Check applied type trees for well-formedness. This means * - all arguments are within their corresponding bounds * - 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. @@ -237,8 +237,7 @@ object Checking { 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 + errorType(i"illegal cyclic reference: ${checker.where} ${checker.lastChecked} of $sym refers back to the type itself", sym.pos) } else info } diff --git a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala index b5ace87d3..4039c8b81 100644 --- a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala +++ b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala @@ -11,6 +11,7 @@ import dotty.tools.dotc.core.Names.Name import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.core.Types._ import dotty.tools.dotc.core.Decorators._ +import ErrorReporting._ object Dynamic { def isDynamicMethod(name: Name): Boolean = @@ -41,10 +42,9 @@ trait Dynamic { self: Typer with Applications => def isNamedArg(arg: untpd.Tree): Boolean = arg match { case NamedArg(_, _) => true; case _ => false } val args = tree.args val dynName = if (args.exists(isNamedArg)) nme.applyDynamicNamed else nme.applyDynamic - if (dynName == nme.applyDynamicNamed && untpd.isWildcardStarArgList(args)) { - ctx.error("applyDynamicNamed does not support passing a vararg parameter", tree.pos) - tree.withType(ErrorType) - } else { + if (dynName == nme.applyDynamicNamed && untpd.isWildcardStarArgList(args)) + errorTree(tree, "applyDynamicNamed does not support passing a vararg parameter") + else { def namedArgTuple(name: String, arg: untpd.Tree) = untpd.Tuple(List(Literal(Constant(name)), arg)) def namedArgs = args.map { case NamedArg(argName, arg) => namedArgTuple(argName.toString, arg) @@ -89,8 +89,7 @@ trait Dynamic { self: Typer with Applications => case TypeApply(Select(qual, name), targs) if !isDynamicMethod(name) => typedDynamicAssign(qual, name, targs) case _ => - ctx.error("reassignment to val", tree.pos) - tree.withType(ErrorType) + errorTree(tree, "reassignment to val") } } diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index a066fc04a..270ad6c8a 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -23,7 +23,7 @@ object ErrorReporting { def errorType(msg: => Message, pos: Position)(implicit ctx: Context): ErrorType = { ctx.error(msg, pos) - ErrorType + new ErrorType(msg) } def cyclicErrorMsg(ex: CyclicReference)(implicit ctx: Context) = { diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index ee2d68278..ca0af0e2b 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -189,9 +189,8 @@ trait TypeAssigner { 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) - ctx.error(ex"$what cannot be accessed as a member of $pre$where.$whyNot", pos) - ErrorType + 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) @@ -215,17 +214,17 @@ trait TypeAssigner { else if (site.derivesFrom(defn.DynamicClass) && !Dynamic.isDynamicMethod(name)) { TryDynamicCallType } else { - if (!site.isErroneous) { + 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 "" - ctx.error( + errorType( if (name == nme.CONSTRUCTOR) ex"$site does not have a constructor" else NotAMember(site, name, kind), pos) } - ErrorType } } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 73084bd86..af0ae0166 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -351,10 +351,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val ownType = if (rawType.exists) ensureAccessible(rawType, superAccess = false, tree.pos) - else { - error(new MissingIdent(tree, kind, name.show), tree.pos) - ErrorType - } + else + errorType(new MissingIdent(tree, kind, name.show), tree.pos) val tree1 = ownType match { case ownType: NamedType if !prefixIsElidable(ownType) => @@ -1970,10 +1968,25 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit else err.typeMismatch(tree1, pt) } + /** If tree has an error type but no errors are reported yet, issue + * the error message stored in the type. + * One way this can happen is if implicit search causes symbols and types + * to be completed. The types are stored by `typedAhead` so that they can be + * retrieved later and thus avoid duplication of typechecking work. + * But if the implicit search causing the `typedAhead` fails locally but + * another alternative succeeds we can be left with an ErrorType in the + * tree that went unreported. A scenario where this happens is i1802.scala. + */ + def ensureReported(tp: Type) = tp match { + case err: ErrorType if !ctx.reporter.hasErrors => ctx.error(err.msg, tree.pos) + case _ => + } + tree match { case _: MemberDef | _: PackageDef | _: Import | _: WithoutTypeOrPos[_] => tree case _ => tree.tpe.widen match { - case _: ErrorType => + case tp: FlexType => + ensureReported(tp) tree case ref: TermRef => pt match { diff --git a/tests/neg/i1802.scala b/tests/neg/i1802.scala new file mode 100644 index 000000000..56da672a8 --- /dev/null +++ b/tests/neg/i1802.scala @@ -0,0 +1,21 @@ +import scala.collection.immutable.List +import scala.reflect.{ ClassTag, classTag } +import scala.language.implicitConversions + +object Exception { + type Catcher[+T] = PartialFunction[Throwable, T] + + def mkCatcher[Ex <: Throwable: ClassTag, T](isDef: Ex => Boolean, f: Ex => T) = new Catcher[T] { + private def downcast(x: Throwable): Option[Ex] = + if (classTag[Ex].runtimeClass.isAssignableFrom(x.getClass)) Some(x.asInstanceOf[Ex]) + else None + + def isDefinedAt(x: Throwable) = downcast(x) exists isDef + def apply(x: Throwable): T = f(downcast(x).get) + } + + def mkThrowableCatcher[T](isDef: Throwable => Boolean, f: Throwable => T) = mkCatcher(isDef, f) + + implicit def throwableSubtypeToCatcher[Ex <: Throwable: ClassTag, T](pf: PartialFunction[Ex, T]) = // error: cyclic reference + mkCatcher(pf.isDefinedAt _, pf.apply _) +} |