summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2012-09-25 18:11:29 -0700
committerPaul Phillips <paulp@improving.org>2012-09-25 18:21:33 -0700
commit9d423c9bb76dddcd080d98f4a05c02856708fc06 (patch)
tree764110f7277097e6acdc6efe5ce44040decd5fcc
parent5499e41157cdb6a81772728525e9c1f44401e7cc (diff)
downloadscala-9d423c9bb76dddcd080d98f4a05c02856708fc06.tar.gz
scala-9d423c9bb76dddcd080d98f4a05c02856708fc06.tar.bz2
scala-9d423c9bb76dddcd080d98f4a05c02856708fc06.zip
Improvements to unchecked warnings.
Closes SI-6275, SI-5762. The comment says is better than I can. /** On pattern matcher checkability: * * Consider a pattern match of this form: (x: X) match { case _: P => } * * There are four possibilities to consider: * [P1] X will always conform to P * [P2] x will never conform to P * [P3] X <: P if some runtime test is true * [P4] X cannot be checked against P * * The first two cases correspond to those when there is enough static * information to say X <: P or that !(X <: P) for all X and P. * The fourth case includes unknown abstract types or structural * refinements appearing within a pattern. * * The third case is the interesting one. We designate another type, XR, * which is essentially the intersection of X and |P|, where |P| is * the erasure of P. If XR <: P, then no warning is emitted. * * Examples of how this info is put to use: * sealed trait A[T] ; class B[T] extends A[T] * def f(x: B[Int]) = x match { case _: A[Int] if true => } * def g(x: A[Int]) = x match { case _: B[Int] => } * * `f` requires no warning because X=B[Int], P=A[Int], and B[Int] <:< A[Int]. * `g` requires no warning because X=A[Int], P=B[Int], XR=B[Int], and B[Int] <:< B[Int]. * XR=B[Int] because a value of type A[Int] which is tested to be a B can * only be a B[Int], due to the definition of B (B[T] extends A[T].) * * This is something like asSeenFrom, only rather than asking what a type looks * like from the point of view of one of its base classes, we ask what it looks * like from the point of view of one of its subclasses. */
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Checkable.scala254
-rw-r--r--test/files/neg/t4302.check2
-rw-r--r--test/files/neg/unchecked.check2
-rw-r--r--test/files/neg/unchecked.scala4
-rw-r--r--test/files/neg/unchecked2.check60
-rw-r--r--test/files/neg/unchecked2.scala37
-rw-r--r--test/files/run/t576.scala10
7 files changed, 248 insertions, 121 deletions
diff --git a/src/compiler/scala/tools/nsc/typechecker/Checkable.scala b/src/compiler/scala/tools/nsc/typechecker/Checkable.scala
index 13dcfaacfe..5363abbae9 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Checkable.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Checkable.scala
@@ -11,6 +11,7 @@ import scala.collection.mutable.ListBuffer
import scala.util.control.ControlThrowable
import symtab.Flags._
import scala.annotation.tailrec
+import Checkability._
/** On pattern matcher checkability:
*
@@ -51,104 +52,181 @@ trait Checkable {
import global._
import definitions._
- trait InferCheckable {
- self: Inferencer =>
+ /** The applied type of class 'to' after inferring anything
+ * possible from the knowledge that 'to' must also be of the
+ * type given in 'from'.
+ */
+ def propagateKnownTypes(from: Type, to: Symbol): Type = {
+ def tparams = to.typeParams
+ val tvars = tparams map (p => TypeVar(p))
+ val tvarType = appliedType(to, tvars: _*)
+ val bases = from.baseClasses filter (to.baseClasses contains _)
- import InferErrorGen._
- private def context = getContext
+ bases foreach { bc =>
+ val tps1 = (from baseType bc).typeArgs
+ val tps2 = (tvarType baseType bc).typeArgs
+ (tps1, tps2).zipped foreach (_ =:= _)
+ }
- // if top-level abstract types can be checked using a classtag extractor, don't warn about them
- def checkCheckable(tree: Tree, typeToTest: Type, typeEnsured: Type, inPattern: Boolean, canRemedy: Boolean = false) = {
- log(s"checkCheckable($tree, $typeToTest, $typeEnsured, inPattern = $inPattern, canRemedy = $canRemedy")
+ val resArgs = tparams zip tvars map {
+ case (_, tvar) if tvar.instValid => tvar.constr.inst
+ case (tparam, _) => tparam.tpe
+ }
+ appliedType(to, resArgs: _*)
+ }
- sealed abstract class TypeConformance(check: (Type, Type) => Boolean) {
- def apply(t1: Type, t2: Type): Boolean = check(t1, t2) && {
- log(s"Skipping unchecked for statically verifiable condition $t1 ${this} $t2")
- true
- }
+ private def isUnwarnableTypeArgSymbol(sym: Symbol) = (
+ sym.isTypeParameterOrSkolem // dummy
+ || (sym.name.toTermName == nme.WILDCARD) // _
+ || nme.isVariableName(sym.name) // type variable
+ || (sym == NonLocalReturnControlClass) // synthetic
+ )
+ private def isUnwarnableTypeArg(arg: Type) = (
+ isUnwarnableTypeArgSymbol(arg.typeSymbolDirect) // has to be direct: see pos/t1439
+ || (arg hasAnnotation UncheckedClass) // @unchecked T
+ )
+
+ private def typeArgsInTopLevelType(tp: Type): List[Type] = {
+ val tps = tp match {
+ case RefinedType(parents, _) => parents flatMap typeArgsInTopLevelType
+ case TypeRef(_, ArrayClass, arg :: Nil) => typeArgsInTopLevelType(arg)
+ case TypeRef(pre, sym, args) => typeArgsInTopLevelType(pre) ++ args
+ case ExistentialType(tparams, underlying) => tparams.map(_.tpe) ++ typeArgsInTopLevelType(underlying)
+ case _ => Nil
+ }
+ tps filterNot isUnwarnableTypeArg
+ }
+
+ private def isReifiableArray(tp: Type): Boolean = tp match {
+ case TypeRef(_, ArrayClass, arg :: Nil) => isReifiableArray(arg)
+ case TypeRef(_, sym, args) => isUnwarnableTypeArg(tp) || (!sym.isAbstractType && args.isEmpty)
+ case _ => false
+ }
+
+ private class CheckabilityChecker(val X: Type, val P: Type) {
+ def Xsym = X.typeSymbol
+ def Psym = P.typeSymbol
+ def XR = propagateKnownTypes(X, Psym)
+ def P1 = X matchesPattern P
+ def P2 = CheckabilityChecker.isNeverSubType(X, P)
+ def P3 = Psym.isClass && ((XR matchesPattern P) || isReifiableArray(P) || P.typeArgs.forall(isUnwarnableTypeArg))
+ def P4 = !(P1 || P2 || P3)
+
+ def summaryString = f"""
+ |Checking checkability of (x: $X) against pattern $P
+ |[P1] $P1%-6s X <: P // $X <: $P
+ |[P2] $P2%-6s X !<: P // $X !<: $P for all X, P
+ |[P3] $P3%-6s XR <: P // $XR <: $P
+ |[P4] $P4%-6s None of the above // !(P1 || P2 || P3)
+ """.stripMargin.trim
+
+ val result = (
+ if (X.isErroneous || P.isErroneous) CheckabilityError
+ else if (P1) StaticallyTrue
+ else if (P2) StaticallyFalse
+ else if (P3) RuntimeCheckable
+ else if (uncheckableType == NoType) {
+ // Avoid warning (except ourselves) if we can't pinpoint the uncheckable type
+ debugwarn("Checkability checker says 'Uncheckable', but uncheckable type cannot be found:\n" + summaryString)
+ CheckabilityError
}
- // I tried to use varianceInType to track the variance implications
- // but I could not make it work.
- case object =:= extends TypeConformance(_ =:= _)
- case object <:< extends TypeConformance(_ <:< _)
- case object >:> extends TypeConformance((t1, t2) => t2 <:< t1)
- case object =!= extends TypeConformance((t1, t2) => false)
-
- var bound: List[Symbol] = Nil
- var warningMessages: List[String] = Nil
-
- def isLocalBinding(sym: Symbol) = (
- sym.isAbstractType && (
- (bound contains sym)
- || (sym.name == tpnme.WILDCARD)
- || {
- val e = context.scope.lookupEntry(sym.name)
- (e ne null) && e.sym == sym && !e.sym.isTypeParameterOrSkolem && e.owner == context.scope
- }
- )
- )
- def check(tp0: Type, pt: Type, conformance: TypeConformance): Boolean = {
- val tp = tp0.normalize
- // Set the warning message to be issued when the top-level call fails.
- def warn(what: String): Boolean = {
- warningMessages ::= what
- false
- }
- def checkArg(param: Symbol, arg: Type) = {
- def conforms = (
- if (param.isCovariant) <:<
- else if (param.isContravariant) >:>
- else =:=
- )
- (arg hasAnnotation UncheckedClass) || {
- arg.withoutAnnotations match {
- case TypeRef(_, sym, args) =>
- ( isLocalBinding(sym)
- || arg.typeSymbol.isTypeParameterOrSkolem
- || (sym.name == tpnme.WILDCARD) // avoid spurious warnings on HK types
- || check(arg, param.tpeHK, conforms)
- || warn("non-variable type argument " + arg)
- )
- case _ =>
- warn("non-variable type argument " + arg)
- }
+ else Uncheckable
+ )
+ lazy val uncheckableType = if (Psym.isAbstractType) P else {
+ val possibles = typeArgsInTopLevelType(P).toSet
+ val opt = possibles find { targ =>
+ // Create a derived type with every possibly uncheckable type replaced
+ // with a WildcardType, except for 'targ'. If !(XR <: derived) then
+ // 'targ' is uncheckable.
+ val derived = P map (tp => if (possibles(tp) && !(tp =:= targ)) WildcardType else tp)
+ !(XR <:< derived)
+ }
+ opt getOrElse NoType
+ }
+
+ def isUncheckable = result == Uncheckable
+ def uncheckableMessage = uncheckableType match {
+ case NoType => "something"
+ case tp @ RefinedType(_, _) => "refinement " + tp
+ case TypeRef(_, sym, _) if sym.isAbstractType => "abstract type " + sym.name
+ case tp => "non-variable type argument " + tp
+ }
+ }
+
+ /** X, P, [P1], etc. are all explained at the top of the file.
+ */
+ private object CheckabilityChecker {
+ private def isNeverSubClass(sym1: Symbol, sym2: Symbol) = (
+ sym1.isClass
+ && sym2.isClass
+ && sym1.isEffectivelyFinal
+ && !(sym1 isSubClass sym2)
+ )
+ private def isNeverSubArgs(tps1: List[Type], tps2: List[Type], tparams: List[Symbol]): Boolean = {
+ def isNeverSubArg(t1: Type, t2: Type, variance: Int) = {
+ if (variance > 0) isNeverSubType(t2, t1)
+ else if (variance < 0) isNeverSubType(t1, t2)
+ else isNeverSameType(t1, t2)
+ }
+ exists3(tps1, tps2, tparams map (_.variance))(isNeverSubArg)
+ }
+ private def isNeverSameType(tp1: Type, tp2: Type): Boolean = (tp1, tp2) match {
+ case (TypeRef(_, sym1, args1), TypeRef(_, sym2, args2)) =>
+ sym1.isClass && sym2.isClass && (sym1 ne sym2) && (sym1.isEffectivelyFinal || sym2.isEffectivelyFinal)
+ case _ =>
+ false
+ }
+ private def isNeverSubType(tp1: Type, tp2: Type): Boolean = (tp1, tp2) match {
+ case (TypeRef(_, sym1, args1), TypeRef(_, sym2, args2)) =>
+ isNeverSubClass(sym1, sym2) || {
+ (sym1 isSubClass sym2) && {
+ val tp1seen = tp1 baseType sym2
+
+ isNeverSubArgs(tp1seen.typeArgs, args2, sym2.typeParams)
}
}
+ case _ => false
+ }
+ }
- // Checking if pt (the expected type of the pattern, and the type
- // we are guaranteed) conforms to tp (the type expressed in the pattern's
- // type test.) If it does, then even if the type being checked for appears
- // to be uncheckable, it is not a warning situation, because it is indeed
- // checked: not at runtime, but statically.
- conformance.apply(pt, tp) || (tp match {
- case SingleType(pre, _) => check(pre, pt, =:=)
- case ExistentialType(quantified, tp1) => bound :::= quantified ; check(tp1, pt, <:<)
- case ThisType(_) | NoPrefix => true
- case RefinedType(parents, decls) if decls.isEmpty => parents forall (p => check(p, pt, <:<))
- case RefinedType(_, _) => warn("refinement " + tp)
- case TypeRef(_, ArrayClass, arg :: Nil) => check(arg, NoType, =!=)
- case TypeRef(_, NonLocalReturnControlClass, _) => true // no way to suppress unchecked warnings on try/catch
- // we only use the extractor for top-level type tests, type arguments remain unchecked
- case TypeRef(_, sym, _) if sym.isAbstractType => isLocalBinding(sym) || canRemedy || warn("abstract type " + tp)
- case TypeRef(_, _, Nil) => false // leaf node
- case TypeRef(pre, sym, args) => forall2(sym.typeParams, args)(checkArg) && check(pre, pt.prefix, =:=)
- case _ => warn("type " + tp)
- })
- }
- typeToTest match {
- // Prohibit top-level type tests for these, but they are
- // acceptable nested (e.g. case Foldable[Nothing] => ... )
+ trait InferCheckable {
+ self: Inferencer =>
+
+ /** TODO: much better error positions.
+ * Kind of stuck right now because they just pass us the one tree.
+ * TODO: Eliminate inPattern, canRemedy, which have no place here.
+ */
+ def checkCheckable(tree: Tree, P0: Type, X: Type, inPattern: Boolean, canRemedy: Boolean = false) {
+ // singleton types not considered here
+ val P = P0.widen
+ P match {
+ // Prohibit top-level type tests for these, but they are ok nested (e.g. case Foldable[Nothing] => ... )
case TypeRef(_, NothingClass | NullClass | AnyValClass, _) =>
- TypePatternOrIsInstanceTestError(tree, typeToTest)
+ InferErrorGen.TypePatternOrIsInstanceTestError(tree, P)
+ // If top-level abstract types can be checked using a classtag extractor, don't warn about them
+ case TypeRef(_, sym, _) if sym.isAbstractType && canRemedy =>
+ ;
case _ =>
- def where = ( if (inPattern) "pattern " else "" ) + typeToTest
- if (check(typeToTest, typeEnsured, =:=)) ()
- // Note that this is a regular warning, not an uncheckedWarning,
- // which is now the province of such notifications as "pattern matcher
- // exceeded its analysis budget."
- else warningMessages foreach (m =>
- context.unit.warning(tree.pos, s"$m in type $where is unchecked since it is eliminated by erasure"))
+ val checker = new CheckabilityChecker(X.widen, P)
+ log(checker.summaryString)
+ if (checker.isUncheckable) {
+ def where = if (inPattern) "pattern " else ""
+ val msg = (
+ if (checker.uncheckableType =:= P) s"abstract type $where$P"
+ else s"${checker.uncheckableMessage} in type $where$P"
+ )
+ getContext.unit.warning(tree.pos, s"$msg is unchecked since it is eliminated by erasure")
+ }
}
}
}
}
+
+private[typechecker] final class Checkability(val value: Int) extends AnyVal { }
+private[typechecker] object Checkability {
+ val StaticallyTrue = new Checkability(0)
+ val StaticallyFalse = new Checkability(1)
+ val RuntimeCheckable = new Checkability(2)
+ val Uncheckable = new Checkability(3)
+ val CheckabilityError = new Checkability(4)
+}
diff --git a/test/files/neg/t4302.check b/test/files/neg/t4302.check
index 327425acb0..450d28bbc5 100644
--- a/test/files/neg/t4302.check
+++ b/test/files/neg/t4302.check
@@ -1,4 +1,4 @@
-t4302.scala:2: error: abstract type T in type T is unchecked since it is eliminated by erasure
+t4302.scala:2: error: abstract type T is unchecked since it is eliminated by erasure
def hasMatch[T](x: AnyRef) = x.isInstanceOf[T]
^
one error found
diff --git a/test/files/neg/unchecked.check b/test/files/neg/unchecked.check
index 34a11db1a0..2883b716c9 100644
--- a/test/files/neg/unchecked.check
+++ b/test/files/neg/unchecked.check
@@ -2,7 +2,7 @@ unchecked.scala:18: error: non-variable type argument String in type pattern Ite
case xs: Iterable[String] => xs.head // unchecked
^
unchecked.scala:22: error: non-variable type argument Any in type pattern Set[Any] is unchecked since it is eliminated by erasure
- case xs: Set[Any] => xs.head // unchecked
+ case xs: Set[Any] => xs.head // unchecked
^
unchecked.scala:26: error: non-variable type argument Any in type pattern Map[Any,Any] is unchecked since it is eliminated by erasure
case xs: Map[Any, Any] => xs.head // unchecked
diff --git a/test/files/neg/unchecked.scala b/test/files/neg/unchecked.scala
index b50cdf9d7a..e491b253ba 100644
--- a/test/files/neg/unchecked.scala
+++ b/test/files/neg/unchecked.scala
@@ -19,8 +19,8 @@ object Test {
case _ => 0
}
def f3(x: Any) = x match {
- case xs: Set[Any] => xs.head // unchecked
- case _ => 0
+ case xs: Set[Any] => xs.head // unchecked
+ case _ => 0
}
def f4(x: Any) = x match {
case xs: Map[Any, Any] => xs.head // unchecked
diff --git a/test/files/neg/unchecked2.check b/test/files/neg/unchecked2.check
index e37865928e..0ff2a249a8 100644
--- a/test/files/neg/unchecked2.check
+++ b/test/files/neg/unchecked2.check
@@ -1,19 +1,43 @@
-unchecked2.scala:2: error: non-variable type argument Int in type Option[Int] is unchecked since it is eliminated by erasure
- Some(123).isInstanceOf[Option[Int]]
- ^
-unchecked2.scala:3: error: non-variable type argument String in type Option[String] is unchecked since it is eliminated by erasure
- Some(123).isInstanceOf[Option[String]]
- ^
unchecked2.scala:4: error: non-variable type argument List[String] in type Option[List[String]] is unchecked since it is eliminated by erasure
- Some(123).isInstanceOf[Option[List[String]]]
- ^
-unchecked2.scala:5: error: non-variable type argument List[Int => String] in type Option[List[Int => String]] is unchecked since it is eliminated by erasure
- Some(123).isInstanceOf[Option[List[Int => String]]]
- ^
-unchecked2.scala:6: error: non-variable type argument (String, Double) in type Option[(String, Double)] is unchecked since it is eliminated by erasure
- Some(123).isInstanceOf[Option[(String, Double)]]
- ^
-unchecked2.scala:7: error: non-variable type argument String => Double in type Option[String => Double] is unchecked since it is eliminated by erasure
- Some(123).isInstanceOf[Option[String => Double]]
- ^
-6 errors found
+ /* warn */ Some(List(1)).isInstanceOf[Option[List[String]]]
+ ^
+unchecked2.scala:5: error: non-variable type argument Option[_] in type Option[Option[_]] is unchecked since it is eliminated by erasure
+ /* warn */ Some(123).isInstanceOf[Option[Option[_]]]
+ ^
+unchecked2.scala:6: error: non-variable type argument String in type Option[String] is unchecked since it is eliminated by erasure
+ /* warn */ Some(123).isInstanceOf[Option[String]]
+ ^
+unchecked2.scala:7: error: non-variable type argument List[String] in type Option[List[String]] is unchecked since it is eliminated by erasure
+ /* warn */ Some(123).isInstanceOf[Option[List[String]]]
+ ^
+unchecked2.scala:8: error: non-variable type argument List[Int => String] in type Option[List[Int => String]] is unchecked since it is eliminated by erasure
+ /* warn */ Some(123).isInstanceOf[Option[List[Int => String]]]
+ ^
+unchecked2.scala:9: error: non-variable type argument (String, Double) in type Option[(String, Double)] is unchecked since it is eliminated by erasure
+ /* warn */ Some(123).isInstanceOf[Option[(String, Double)]]
+ ^
+unchecked2.scala:10: error: non-variable type argument String => Double in type Option[String => Double] is unchecked since it is eliminated by erasure
+ /* warn */ Some(123).isInstanceOf[Option[String => Double]]
+ ^
+unchecked2.scala:14: error: non-variable type argument List[String] in type Option[List[String]] is unchecked since it is eliminated by erasure
+ /* warn */ (Some(List(1)): Any).isInstanceOf[Option[List[String]]]
+ ^
+unchecked2.scala:15: error: non-variable type argument Int in type Option[Int] is unchecked since it is eliminated by erasure
+ /* warn */ (Some(123): Any).isInstanceOf[Option[Int]]
+ ^
+unchecked2.scala:16: error: non-variable type argument String in type Option[String] is unchecked since it is eliminated by erasure
+ /* warn */ (Some(123): Any).isInstanceOf[Option[String]]
+ ^
+unchecked2.scala:17: error: non-variable type argument List[String] in type Option[List[String]] is unchecked since it is eliminated by erasure
+ /* warn */ (Some(123): Any).isInstanceOf[Option[List[String]]]
+ ^
+unchecked2.scala:18: error: non-variable type argument List[Int => String] in type Option[List[Int => String]] is unchecked since it is eliminated by erasure
+ /* warn */ (Some(123): Any).isInstanceOf[Option[List[Int => String]]]
+ ^
+unchecked2.scala:19: error: non-variable type argument (String, Double) in type Option[(String, Double)] is unchecked since it is eliminated by erasure
+ /* warn */ (Some(123): Any).isInstanceOf[Option[(String, Double)]]
+ ^
+unchecked2.scala:20: error: non-variable type argument String => Double in type Option[String => Double] is unchecked since it is eliminated by erasure
+ /* warn */ (Some(123): Any).isInstanceOf[Option[String => Double]]
+ ^
+14 errors found
diff --git a/test/files/neg/unchecked2.scala b/test/files/neg/unchecked2.scala
index a2e757e1dc..616b05aad8 100644
--- a/test/files/neg/unchecked2.scala
+++ b/test/files/neg/unchecked2.scala
@@ -1,8 +1,33 @@
object Test {
- Some(123).isInstanceOf[Option[Int]]
- Some(123).isInstanceOf[Option[String]]
- Some(123).isInstanceOf[Option[List[String]]]
- Some(123).isInstanceOf[Option[List[Int => String]]]
- Some(123).isInstanceOf[Option[(String, Double)]]
- Some(123).isInstanceOf[Option[String => Double]]
+ // These warn because it can be statically shown they won't match.
+
+ /* warn */ Some(List(1)).isInstanceOf[Option[List[String]]]
+ /* warn */ Some(123).isInstanceOf[Option[Option[_]]]
+ /* warn */ Some(123).isInstanceOf[Option[String]]
+ /* warn */ Some(123).isInstanceOf[Option[List[String]]]
+ /* warn */ Some(123).isInstanceOf[Option[List[Int => String]]]
+ /* warn */ Some(123).isInstanceOf[Option[(String, Double)]]
+ /* warn */ Some(123).isInstanceOf[Option[String => Double]]
+
+ // These warn because you can't check at runtime.
+
+ /* warn */ (Some(List(1)): Any).isInstanceOf[Option[List[String]]]
+ /* warn */ (Some(123): Any).isInstanceOf[Option[Int]]
+ /* warn */ (Some(123): Any).isInstanceOf[Option[String]]
+ /* warn */ (Some(123): Any).isInstanceOf[Option[List[String]]]
+ /* warn */ (Some(123): Any).isInstanceOf[Option[List[Int => String]]]
+ /* warn */ (Some(123): Any).isInstanceOf[Option[(String, Double)]]
+ /* warn */ (Some(123): Any).isInstanceOf[Option[String => Double]]
+
+ // These don't warn.
+
+ /* nowarn */ Some(List(1)).isInstanceOf[Option[List[Int]]]
+ /* nowarn */ Some(123).isInstanceOf[Option[Int]]
+ /* nowarn */ Some(123).isInstanceOf[Some[Int]]
+ /* nowarn */ Some(123).isInstanceOf[AnyRef]
+
+ /* nowarn */ (Some(List(1)): Any).isInstanceOf[Option[_]]
+ /* nowarn */ (Some(123): Any).isInstanceOf[Option[_]]
+ /* nowarn */ (Some(123): Any).isInstanceOf[Some[_]]
+ /* nowarn */ (Some(123): Any).isInstanceOf[AnyRef]
}
diff --git a/test/files/run/t576.scala b/test/files/run/t576.scala
index dc09d8dc98..756a241572 100644
--- a/test/files/run/t576.scala
+++ b/test/files/run/t576.scala
@@ -12,7 +12,7 @@ object Dingus {
object Test {
val x1 = new A
val x2 = new A
-
+
val x3 = new { self =>
override def equals(other : Any) = other match {
case that: self.type => true
@@ -20,7 +20,7 @@ object Test {
}
}
val x4 = new { self =>
- def f(x: Any) = x match {
+ def f(x: Any): Int = x match {
case _: x1.type => 1
case _: x2.type => 2
case _: x3.type => 3
@@ -35,11 +35,11 @@ object Test {
assert(x1 != x2)
assert(x1 != ())
assert(x2 != x1)
-
+
assert(x3 == x3)
assert(x3 != x2)
assert(x2 != x3)
-
+
List(x1, x2, x3, x4, Dingus) map x4.f foreach println
}
-} \ No newline at end of file
+}