Skip to content
This repository
Browse code

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.
  • Loading branch information...
commit 692372ce1d82d0ba41461b9539fdc85238477464 1 parent 9ea0a20
Jason Zaugg retronym authored
2  src/compiler/scala/tools/nsc/matching/Patterns.scala
@@ -402,7 +402,7 @@ trait Patterns extends ast.TreeDSL {
402 402 case _ => toPats(args)
403 403 }
404 404
405   - def resTypes = analyzer.unapplyTypeList(unfn.symbol, unfn.tpe, args.length)
  405 + def resTypes = analyzer.unapplyTypeList(unfn.pos, unfn.symbol, unfn.tpe, args.length)
406 406 def resTypesString = resTypes match {
407 407 case Nil => "Boolean"
408 408 case xs => xs.mkString(", ")
2  src/compiler/scala/tools/nsc/transform/UnCurry.scala
@@ -613,7 +613,7 @@ abstract class UnCurry extends InfoTransform
613 613 val fn1 = withInPattern(false)(transform(fn))
614 614 val args1 = transformTrees(fn.symbol.name match {
615 615 case nme.unapply => args
616   - case nme.unapplySeq => transformArgs(tree.pos, fn.symbol, args, analyzer.unapplyTypeList(fn.symbol, fn.tpe, args.length))
  616 + case nme.unapplySeq => transformArgs(tree.pos, fn.symbol, args, analyzer.unapplyTypeList(fn.pos, fn.symbol, fn.tpe, args.length))
617 617 case _ => sys.error("internal error: UnApply node has wrong symbol")
618 618 })
619 619 treeCopy.UnApply(tree, fn1, args1)
15 src/compiler/scala/tools/nsc/typechecker/Infer.scala
@@ -73,7 +73,7 @@ trait Infer extends Checkable {
73 73 * n > 1 and unapply’s result type is Option[(T1, ..., Tn)], for some types T1, ..., Tn.
74 74 * the argument patterns p1, ..., pn are typed in turn with expected types T1, ..., Tn
75 75 */
76   - def extractorFormalTypes(resTp: Type, nbSubPats: Int, unappSym: Symbol): (List[Type], List[Type]) = {
  76 + def extractorFormalTypes(pos: Position, resTp: Type, nbSubPats: Int, unappSym: Symbol): (List[Type], List[Type]) = {
77 77 val isUnapplySeq = unappSym.name == nme.unapplySeq
78 78 val booleanExtractor = resTp.typeSymbolDirect == BooleanClass
79 79
@@ -87,11 +87,18 @@ trait Infer extends Checkable {
87 87 if (nbSubPats == 0 && booleanExtractor && !isUnapplySeq) Nil
88 88 else resTp.baseType(OptionClass).typeArgs match {
89 89 case optionTArg :: Nil =>
90   - if (nbSubPats == 1)
  90 + def productArgs = getProductArgs(optionTArg)
  91 + if (nbSubPats == 1) {
91 92 if (isUnapplySeq) List(seqToRepeatedChecked(optionTArg))
92   - else List(optionTArg)
  93 + else {
  94 + val productArity = productArgs.size
  95 + if (productArity > 1 && settings.lint.value)
  96 + global.currentUnit.warning(pos, s"extractor pattern binds a single value to a Product${productArity} of type ${optionTArg}")
  97 + List(optionTArg)
  98 + }
  99 + }
93 100 // TODO: update spec to reflect we allow any ProductN, not just TupleN
94   - else getProductArgs(optionTArg) match {
  101 + else productArgs match {
95 102 case Nil if isUnapplySeq => List(seqToRepeatedChecked(optionTArg))
96 103 case tps if isUnapplySeq => tps.init :+ seqToRepeatedChecked(tps.last)
97 104 case tps => tps
4 src/compiler/scala/tools/nsc/typechecker/Typers.scala
@@ -3465,7 +3465,7 @@ trait Typers extends Modes with Adaptations with Tags {
3465 3465 val resTp = fun1.tpe.finalResultType.normalize
3466 3466 val nbSubPats = args.length
3467 3467
3468   - val (formals, formalsExpanded) = extractorFormalTypes(resTp, nbSubPats, fun1.symbol)
  3468 + val (formals, formalsExpanded) = extractorFormalTypes(fun0.pos, resTp, nbSubPats, fun1.symbol)
3469 3469 if (formals == null) duplErrorTree(WrongNumberOfArgsError(tree, fun))
3470 3470 else {
3471 3471 val args1 = typedArgs(args, mode, formals, formalsExpanded)
@@ -5311,7 +5311,7 @@ trait Typers extends Modes with Adaptations with Tags {
5311 5311
5312 5312 def typedUnApply(tree: UnApply) = {
5313 5313 val fun1 = typed(tree.fun)
5314   - val tpes = formalTypes(unapplyTypeList(tree.fun.symbol, fun1.tpe, tree.args.length), tree.args.length)
  5314 + val tpes = formalTypes(unapplyTypeList(tree.fun.pos, tree.fun.symbol, fun1.tpe, tree.args.length), tree.args.length)
5315 5315 val args1 = map2(tree.args, tpes)(typedPattern)
5316 5316 treeCopy.UnApply(tree, fun1, args1) setType pt
5317 5317 }
4 src/compiler/scala/tools/nsc/typechecker/Unapplies.scala
@@ -34,12 +34,12 @@ trait Unapplies extends ast.TreeDSL
34 34 /** returns type list for return type of the extraction
35 35 * @see extractorFormalTypes
36 36 */
37   - def unapplyTypeList(ufn: Symbol, ufntpe: Type, nbSubPats: Int) = {
  37 + def unapplyTypeList(pos: Position, ufn: Symbol, ufntpe: Type, nbSubPats: Int) = {
38 38 assert(ufn.isMethod, ufn)
39 39 //Console.println("utl "+ufntpe+" "+ufntpe.typeSymbol)
40 40 ufn.name match {
41 41 case nme.unapply | nme.unapplySeq =>
42   - val (formals, _) = extractorFormalTypes(unapplyUnwrap(ufntpe), nbSubPats, ufn)
  42 + val (formals, _) = extractorFormalTypes(pos, unapplyUnwrap(ufntpe), nbSubPats, ufn)
43 43 if (formals == null) throw new TypeError(s"$ufn of type $ufntpe cannot extract $nbSubPats sub-patterns")
44 44 else formals
45 45 case _ => throw new TypeError(ufn+" is not an unapply or unapplySeq")
4 test/files/neg/t6675.check
... ... @@ -0,0 +1,4 @@
  1 +t6675.scala:10: error: extractor pattern binds a single value to a Product3 of type (Int, Int, Int)
  2 + "" match { case X(b) => b } // should warn under -Xlint. Not an error because of SI-6111
  3 + ^
  4 +one error found
1  test/files/neg/t6675.flags
... ... @@ -0,0 +1 @@
  1 +-Xlint -Xfatal-warnings
13 test/files/neg/t6675.scala
... ... @@ -0,0 +1,13 @@
  1 +object X {
  2 + def unapply(s: String): Option[(Int,Int,Int)] = Some((1,2,3))
  3 +}
  4 +
  5 +object Y {
  6 + def unapplySeq(s: String): Option[Seq[(Int,Int,Int)]] = Some(Seq((1,2,3)))
  7 +}
  8 +
  9 +object Test {
  10 + "" match { case X(b) => b } // should warn under -Xlint. Not an error because of SI-6111
  11 +
  12 + "" match { case Y(b) => b } // no warning
  13 +}

0 comments on commit 692372c

Please sign in to comment.
Something went wrong with that request. Please try again.