From 9248f230a51150a06ea0e9efa1879b49a3253732 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 8 Jun 2012 13:11:17 +0200 Subject: don't warn about abstract types that are checkable --- .../scala/tools/nsc/typechecker/Infer.scala | 12 ++- .../scala/tools/nsc/typechecker/Typers.scala | 93 ++++++++++++---------- 2 files changed, 58 insertions(+), 47 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index 1ed9350d3f..9e371dd2dd 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -1247,7 +1247,10 @@ trait Infer { check(tp, Nil) } - def checkCheckable(tree: Tree, tp: Type, kind: String) { + // if top-level abstract types can be checked using a classtag extractor, don't warn about them + def checkCheckable(tree: Tree, tp: Type, inPattern: Boolean, canRemedy: Boolean = false) = { + val kind = if (inPattern) "pattern " else "" + def patternWarning(tp0: Type, prefix: String) = { context.unit.uncheckedWarning(tree.pos, prefix+tp0+" in type "+kind+tp+" is unchecked since it is eliminated by erasure") } @@ -1264,7 +1267,8 @@ trait Infer { check(pre, bound) case TypeRef(pre, sym, args) => if (sym.isAbstractType) { - if (!isLocalBinding(sym)) patternWarning(tp, "abstract type ") + // we only use the extractor for top-level type tests, type arguments (see below) remain unchecked + if (!isLocalBinding(sym) && !canRemedy) patternWarning(tp, "abstract type ") } else if (sym.isAliasType) { check(tp.normalize, bound) } else if (sym == NothingClass || sym == NullClass || sym == AnyValClass) { @@ -1320,7 +1324,7 @@ trait Infer { } } - def inferTypedPattern(tree0: Tree, pattp: Type, pt0: Type): Type = { + def inferTypedPattern(tree0: Tree, pattp: Type, pt0: Type, canRemedy: Boolean): Type = { val pt = widen(pt0) val ptparams = freeTypeParamsOfTerms(pt) val tpparams = freeTypeParamsOfTerms(pattp) @@ -1337,7 +1341,7 @@ trait Infer { return ErrorType } - checkCheckable(tree0, pattp, "pattern ") + checkCheckable(tree0, pattp, inPattern = true, canRemedy) if (pattp <:< pt) () else { debuglog("free type params (1) = " + tpparams) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 9a27f3dd41..e9bf6dab3d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -970,7 +970,7 @@ trait Typers extends Modes with Adaptations with Tags { val overloadedExtractorOfObject = tree.symbol filter (sym => hasUnapplyMember(sym.tpe)) // if the tree's symbol's type does not define an extractor, maybe the tree's type does // this is the case when we encounter an arbitrary tree as the target of an unapply call (rather than something that looks like a constructor call) - // (for now, this only happens due to maybeWrapClassTagUnapply, but when we support parameterized extractors, it will become more common place) + // (for now, this only happens due to wrapClassTagUnapply, but when we support parameterized extractors, it will become more common place) val extractor = overloadedExtractorOfObject orElse unapplyMember(tree.tpe) if (extractor != NoSymbol) { // if we did some ad-hoc overloading resolution, update the tree's symbol @@ -3088,6 +3088,11 @@ trait Typers extends Modes with Adaptations with Tags { val argDummy = context.owner.newValue(nme.SELECTOR_DUMMY, fun.pos, SYNTHETIC) setInfo pt val arg = Ident(argDummy) setType pt + val uncheckedTypeExtractor = + if (unappType.paramTypes.nonEmpty) + extractorForUncheckedType(tree.pos, unappType.paramTypes.head) + else None + if (!isApplicableSafe(Nil, unappType, List(pt), WildcardType)) { //Console.println("UNAPP: need to typetest, arg.tpe = "+arg.tpe+", unappType = "+unappType) val (freeVars, unappFormal) = freshArgType(unappType.skolemizeExistential(context.owner, tree)) @@ -3095,7 +3100,7 @@ trait Typers extends Modes with Adaptations with Tags { freeVars foreach unapplyContext.scope.enter val typer1 = newTyper(unapplyContext) - val pattp = typer1.infer.inferTypedPattern(tree, unappFormal, arg.tpe) + val pattp = typer1.infer.inferTypedPattern(tree, unappFormal, arg.tpe, canRemedy = uncheckedTypeExtractor.nonEmpty) // turn any unresolved type variables in freevars into existential skolems val skolems = freeVars map (fv => unapplyContext.owner.newExistentialSkolem(fv, fv)) @@ -3124,57 +3129,53 @@ trait Typers extends Modes with Adaptations with Tags { // if the type that the unapply method expects for its argument is uncheckable, wrap in classtag extractor // skip if the unapply's type is not a method type with (at least, but really it should be exactly) one argument - if (unappType.paramTypes.isEmpty) unapply - else maybeWrapClassTagUnapply(unapply, unappType.paramTypes.head) + // also skip if we already wrapped a classtag extractor (so we don't keep doing that forever) + if (uncheckedTypeExtractor.isEmpty || fun1.symbol.owner.isNonBottomSubClass(ClassTagClass)) unapply + else wrapClassTagUnapply(unapply, uncheckedTypeExtractor.get, unappType.paramTypes.head) } else duplErrorTree(WrongNumberArgsPatternError(tree, fun)) } } - // TODO: disable when in unchecked match - def maybeWrapClassTagUnapply(uncheckedPattern: Tree, pt: Type): Tree = if (isPastTyper) uncheckedPattern else { + def wrapClassTagUnapply(uncheckedPattern: Tree, classTagExtractor: Tree, pt: Type): Tree = { + // TODO: disable when in unchecked match + // we don't create a new Context for a Match, so find the CaseDef, then go out one level and navigate back to the match that has this case + // val thisCase = context.nextEnclosing(_.tree.isInstanceOf[CaseDef]) + // val unchecked = thisCase.outer.tree.collect{case Match(selector, cases) if cases contains thisCase => selector} match { + // case List(Typed(_, tpt)) if treeInfo.isUncheckedAnnotation(tpt.tpe) => true + // case t => println("outer tree: "+ (t, thisCase, thisCase.outer.tree)); false + // } + // println("wrapClassTagUnapply"+ (!isPastTyper && infer.containsUnchecked(pt), pt, uncheckedPattern)) + // println("wrapClassTagUnapply: "+ extractor) + // println(util.Position.formatMessage(uncheckedPattern.pos, "made unchecked type test into a checked one", true)) + + val args = List(uncheckedPattern) + // must call doTypedUnapply directly, as otherwise we get undesirable rewrites + // and re-typechecks of the target of the unapply call in PATTERNmode, + // this breaks down when the classTagExtractor (which defineds the unapply member) is not a simple reference to an object, + // but an arbitrary tree as is the case here + doTypedUnapply(Apply(classTagExtractor, args), classTagExtractor, classTagExtractor, args, PATTERNmode, pt) + } + + // if there's a ClassTag that allows us to turn the unchecked type test for `pt` into a checked type test + // return the corresponding extractor (an instance of ClassTag[`pt`]) + def extractorForUncheckedType(pos: Position, pt: Type): Option[Tree] = if (!opt.virtPatmat || isPastTyper) None else { // only look at top-level type, can't (reliably) do anything about unchecked type args (in general) pt.normalize.typeConstructor match { // if at least one of the types in an intersection is checkable, use the checkable ones // this avoids problems as in run/matchonseq.scala, where the expected type is `Coll with scala.collection.SeqLike` // Coll is an abstract type, but SeqLike of course is not - case tp@RefinedType(parents, _) if (parents.length >= 2) && (parents.exists(tp => !infer.containsUnchecked(tp))) => - uncheckedPattern + case RefinedType(parents, _) if (parents.length >= 2) && (parents.exists(tp => !infer.containsUnchecked(tp))) => + None case ptCheckable if infer.containsUnchecked(ptCheckable) => - uncheckedPattern match { - // are we being called on a classtag extractor call? - // don't recurse forever, it's not *that* much fun - case UnApply(fun, _) if fun.symbol.owner.isNonBottomSubClass(ClassTagClass) => - uncheckedPattern - - case _ => - val typeTagExtractor = resolveClassTag(uncheckedPattern.pos, ptCheckable) - //orElse resolveTypeTag(uncheckedPattern.pos, ReflectMirrorPrefix.tpe, ptCheckable, concrete = true) - - // we don't create a new Context for a Match, so find the CaseDef, then go out one level and navigate back to the match that has this case - // val thisCase = context.nextEnclosing(_.tree.isInstanceOf[CaseDef]) - // val unchecked = thisCase.outer.tree.collect{case Match(selector, cases) if cases contains thisCase => selector} match { - // case List(Typed(_, tpt)) if treeInfo.isUncheckedAnnotation(tpt.tpe) => true - // case t => println("outer tree: "+ (t, thisCase, thisCase.outer.tree)); false - // } - // println("maybeWrapClassTagUnapply"+ (!isPastTyper && infer.containsUnchecked(pt), pt, uncheckedPattern)) - // println("typeTagExtractor"+ typeTagExtractor) - - - if (typeTagExtractor != EmptyTree && unapplyMember(typeTagExtractor.tpe) != NoSymbol) { - // println("maybeWrapClassTagUnapply "+ typeTagExtractor) - // println(util.Position.formatMessage(uncheckedPattern.pos, "made unchecked type test into a checked one", true)) - val args = List(uncheckedPattern) - // must call doTypedUnapply directly, as otherwise we get undesirable rewrites - // and re-typechecks of the target of the unapply call in PATTERNmode, - // this breaks down when the extractor (which defineds the unapply member) is not a simple reference to an object, - // but an arbitrary tree as is the case here - doTypedUnapply(Apply(typeTagExtractor, args), typeTagExtractor, typeTagExtractor, args, PATTERNmode, pt) - } else uncheckedPattern - } + val classTagExtractor = resolveClassTag(pos, ptCheckable) + + if (classTagExtractor != EmptyTree && unapplyMember(classTagExtractor.tpe) != NoSymbol) + Some(classTagExtractor) + else None - case _ => uncheckedPattern + case _ => None } } @@ -3618,7 +3619,7 @@ trait Typers extends Modes with Adaptations with Tags { typedClassOf(tree, args.head, true) else { if (!isPastTyper && fun.symbol == Any_isInstanceOf && !targs.isEmpty) - checkCheckable(tree, targs.head, "") + checkCheckable(tree, targs.head, inPattern = false) val resultpe = restpe.instantiateTypeParams(tparams, targs) //@M substitution in instantiateParams needs to be careful! //@M example: class Foo[a] { def foo[m[x]]: m[a] = error("") } (new Foo[Int]).foo[List] : List[Int] @@ -4880,8 +4881,14 @@ trait Typers extends Modes with Adaptations with Tags { val treeTyped = treeCopy.Typed(tree, exprTyped, tptTyped) if (isPatternMode) { - val ownType = inferTypedPattern(tptTyped, tptTyped.tpe, pt) - maybeWrapClassTagUnapply(treeTyped setType ownType, tptTyped.tpe) + val uncheckedTypeExtractor = extractorForUncheckedType(tpt.pos, tptTyped.tpe) + val ownType = inferTypedPattern(tptTyped, tptTyped.tpe, pt, canRemedy = uncheckedTypeExtractor.nonEmpty) + treeTyped setType ownType + + uncheckedTypeExtractor match { + case None => treeTyped + case Some(extractor) => wrapClassTagUnapply(treeTyped, extractor, tptTyped.tpe) + } } else treeTyped setType tptTyped.tpe -- cgit v1.2.3