From 692372ce1d82d0ba41461b9539fdc85238477464 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 13 Jan 2013 12:36:36 +0100 Subject: SI-6675 -Xlint arity enforcement for extractors Extractor Patterns changed in 2.10.0 to implement the letter of the spec, which allows a single binding to capture an entire TupleN. But this can hide arity mismatches, especially if the case body uses the bound value as an `Any`. This change warns when this happens under -Xlint. --- src/compiler/scala/tools/nsc/matching/Patterns.scala | 2 +- src/compiler/scala/tools/nsc/transform/UnCurry.scala | 2 +- src/compiler/scala/tools/nsc/typechecker/Infer.scala | 15 +++++++++++---- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 4 ++-- src/compiler/scala/tools/nsc/typechecker/Unapplies.scala | 4 ++-- test/files/neg/t6675.check | 4 ++++ test/files/neg/t6675.flags | 1 + test/files/neg/t6675.scala | 13 +++++++++++++ 8 files changed, 35 insertions(+), 10 deletions(-) create mode 100644 test/files/neg/t6675.check create mode 100644 test/files/neg/t6675.flags create mode 100644 test/files/neg/t6675.scala diff --git a/src/compiler/scala/tools/nsc/matching/Patterns.scala b/src/compiler/scala/tools/nsc/matching/Patterns.scala index 99b72fa26e..f116a7c4c7 100644 --- a/src/compiler/scala/tools/nsc/matching/Patterns.scala +++ b/src/compiler/scala/tools/nsc/matching/Patterns.scala @@ -402,7 +402,7 @@ trait Patterns extends ast.TreeDSL { case _ => toPats(args) } - def resTypes = analyzer.unapplyTypeList(unfn.symbol, unfn.tpe, args.length) + def resTypes = analyzer.unapplyTypeList(unfn.pos, unfn.symbol, unfn.tpe, args.length) def resTypesString = resTypes match { case Nil => "Boolean" case xs => xs.mkString(", ") diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index 838ea7d5a0..ff08fe4ffa 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -613,7 +613,7 @@ abstract class UnCurry extends InfoTransform val fn1 = withInPattern(false)(transform(fn)) val args1 = transformTrees(fn.symbol.name match { case nme.unapply => args - case nme.unapplySeq => transformArgs(tree.pos, fn.symbol, args, analyzer.unapplyTypeList(fn.symbol, fn.tpe, args.length)) + case nme.unapplySeq => transformArgs(tree.pos, fn.symbol, args, analyzer.unapplyTypeList(fn.pos, fn.symbol, fn.tpe, args.length)) case _ => sys.error("internal error: UnApply node has wrong symbol") }) treeCopy.UnApply(tree, fn1, args1) diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index 0f52687c75..a3c0f10612 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -73,7 +73,7 @@ trait Infer extends Checkable { * n > 1 and unapply’s result type is Option[(T1, ..., Tn)], for some types T1, ..., Tn. * the argument patterns p1, ..., pn are typed in turn with expected types T1, ..., Tn */ - def extractorFormalTypes(resTp: Type, nbSubPats: Int, unappSym: Symbol): (List[Type], List[Type]) = { + def extractorFormalTypes(pos: Position, resTp: Type, nbSubPats: Int, unappSym: Symbol): (List[Type], List[Type]) = { val isUnapplySeq = unappSym.name == nme.unapplySeq val booleanExtractor = resTp.typeSymbolDirect == BooleanClass @@ -87,11 +87,18 @@ trait Infer extends Checkable { if (nbSubPats == 0 && booleanExtractor && !isUnapplySeq) Nil else resTp.baseType(OptionClass).typeArgs match { case optionTArg :: Nil => - if (nbSubPats == 1) + def productArgs = getProductArgs(optionTArg) + if (nbSubPats == 1) { if (isUnapplySeq) List(seqToRepeatedChecked(optionTArg)) - else List(optionTArg) + else { + val productArity = productArgs.size + if (productArity > 1 && settings.lint.value) + global.currentUnit.warning(pos, s"extractor pattern binds a single value to a Product${productArity} of type ${optionTArg}") + List(optionTArg) + } + } // TODO: update spec to reflect we allow any ProductN, not just TupleN - else getProductArgs(optionTArg) match { + else productArgs match { case Nil if isUnapplySeq => List(seqToRepeatedChecked(optionTArg)) case tps if isUnapplySeq => tps.init :+ seqToRepeatedChecked(tps.last) case tps => tps diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 386eec207a..f2f02ba54e 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -3465,7 +3465,7 @@ trait Typers extends Modes with Adaptations with Tags { val resTp = fun1.tpe.finalResultType.normalize val nbSubPats = args.length - val (formals, formalsExpanded) = extractorFormalTypes(resTp, nbSubPats, fun1.symbol) + val (formals, formalsExpanded) = extractorFormalTypes(fun0.pos, resTp, nbSubPats, fun1.symbol) if (formals == null) duplErrorTree(WrongNumberOfArgsError(tree, fun)) else { val args1 = typedArgs(args, mode, formals, formalsExpanded) @@ -5311,7 +5311,7 @@ trait Typers extends Modes with Adaptations with Tags { def typedUnApply(tree: UnApply) = { val fun1 = typed(tree.fun) - val tpes = formalTypes(unapplyTypeList(tree.fun.symbol, fun1.tpe, tree.args.length), tree.args.length) + val tpes = formalTypes(unapplyTypeList(tree.fun.pos, tree.fun.symbol, fun1.tpe, tree.args.length), tree.args.length) val args1 = map2(tree.args, tpes)(typedPattern) treeCopy.UnApply(tree, fun1, args1) setType pt } diff --git a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala index a34d7389bf..5782d7bbca 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala @@ -34,12 +34,12 @@ trait Unapplies extends ast.TreeDSL /** returns type list for return type of the extraction * @see extractorFormalTypes */ - def unapplyTypeList(ufn: Symbol, ufntpe: Type, nbSubPats: Int) = { + def unapplyTypeList(pos: Position, ufn: Symbol, ufntpe: Type, nbSubPats: Int) = { assert(ufn.isMethod, ufn) //Console.println("utl "+ufntpe+" "+ufntpe.typeSymbol) ufn.name match { case nme.unapply | nme.unapplySeq => - val (formals, _) = extractorFormalTypes(unapplyUnwrap(ufntpe), nbSubPats, ufn) + val (formals, _) = extractorFormalTypes(pos, unapplyUnwrap(ufntpe), nbSubPats, ufn) if (formals == null) throw new TypeError(s"$ufn of type $ufntpe cannot extract $nbSubPats sub-patterns") else formals case _ => throw new TypeError(ufn+" is not an unapply or unapplySeq") diff --git a/test/files/neg/t6675.check b/test/files/neg/t6675.check new file mode 100644 index 0000000000..7b271de213 --- /dev/null +++ b/test/files/neg/t6675.check @@ -0,0 +1,4 @@ +t6675.scala:10: error: extractor pattern binds a single value to a Product3 of type (Int, Int, Int) + "" match { case X(b) => b } // should warn under -Xlint. Not an error because of SI-6111 + ^ +one error found diff --git a/test/files/neg/t6675.flags b/test/files/neg/t6675.flags new file mode 100644 index 0000000000..e93641e931 --- /dev/null +++ b/test/files/neg/t6675.flags @@ -0,0 +1 @@ +-Xlint -Xfatal-warnings \ No newline at end of file diff --git a/test/files/neg/t6675.scala b/test/files/neg/t6675.scala new file mode 100644 index 0000000000..4d500b77ba --- /dev/null +++ b/test/files/neg/t6675.scala @@ -0,0 +1,13 @@ +object X { + def unapply(s: String): Option[(Int,Int,Int)] = Some((1,2,3)) +} + +object Y { + def unapplySeq(s: String): Option[Seq[(Int,Int,Int)]] = Some(Seq((1,2,3))) +} + +object Test { + "" match { case X(b) => b } // should warn under -Xlint. Not an error because of SI-6111 + + "" match { case Y(b) => b } // no warning +} -- cgit v1.2.3