Skip to content

Commit

Permalink
Loosey-goosey implication in selector
Browse files Browse the repository at this point in the history
If the shadowed element appears anywhere in the selector expression,
don't warn about the shadowing pattern variable.

This means that there might be no type relationship between the two
symbols, and that elements of tuples might be swapped, i.e.,
there is no ordering relationship between arguments in the selector
and in the pattern.

For example, `(x, y) match { case (y, x) => }`.

For example, `t.toString match { case t => }`.
  • Loading branch information
som-snytt committed Jan 16, 2024
1 parent 43c386c commit e534c44
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 58 deletions.
82 changes: 42 additions & 40 deletions src/compiler/scala/tools/nsc/typechecker/RefChecks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2046,28 +2046,48 @@ abstract class RefChecks extends Transform {
if (isMultiline && settings.warnByNameImplicit) checkImplicitlyAdaptedBlockResult(expr)

tree
case Match(selector0, cases) =>
val selector = selector0 match {
case Typed(expr, _) => expr
case _ => selector0
case Match(selector, cases) =>
// only warn if it could be put in backticks in a pattern
def isWarnable(sym: Symbol): Boolean =
sym != null && sym.exists &&
!sym.hasPackageFlag && sym.isStable && !isByName(sym) &&
!sym.hasAttachment[PatVarDefAttachment.type] // val (_, v) = with one var is shadowed in desugaring
//!toCheck.isSynthetic // self-type symbols are synthetic: value self (<synthetic>), do warn

class CheckSelector extends InternalTraverser {
var selectorSymbols: List[Symbol] = null
override def traverse(t: Tree): Unit = {
val include = t match {
case _: This => true // !t.symbol.isStable
case _: SymTree => isWarnable(t.symbol)
case _ => false
}
if (include) selectorSymbols ::= t.symbol
t.traverse(this)
}
// true if the shadowed toCheck appears in the selector expression
def implicatesSelector(toCheck: Symbol): Boolean = {
if (selectorSymbols == null) {
selectorSymbols = Nil
apply(selector)
}
selectorSymbols.exists(sym => sym.eq(toCheck) || sym.accessedOrSelf.eq(toCheck.accessedOrSelf) ||
toCheck.isThisSym && toCheck.owner == sym) // self match { case self: S => }, selector C.this is class symbol
}
}
def notNull(s: Symbol) = if (s != null) s else NoSymbol
val selectorSymbol = notNull(selector.symbol)
val checkSelector = new CheckSelector
// true to warn about shadowed when selSym is the scrutinee
def checkShadowed(shadowed: Symbol)(selSym: Symbol): Boolean = {
def checkShadowed(shadowed: Symbol): Boolean = {
def checkShadowedSymbol(toCheck: Symbol): Boolean =
//!toCheck.isSynthetic && // self-type symbols are synthetic: value self (<synthetic>)
!toCheck.hasPackageFlag && toCheck.isStable && !isByName(toCheck) &&
!toCheck.hasAttachment[PatVarDefAttachment.type] && // val (_, v) = with one var
(selSym.eq(NoSymbol) || // following checks if selector symbol exists
toCheck.ne(selSym) && toCheck.accessedOrSelf.ne(selSym.accessedOrSelf) &&
!(toCheck.isThisSym && toCheck.owner == selSym)) // self match { case self: S => }, selector C.this is class symbol
if (shadowed.isOverloaded) shadowed.alternatives.exists(checkShadowedSymbol) else checkShadowedSymbol(shadowed)
isWarnable(toCheck) && !checkSelector.implicatesSelector(toCheck)

if (shadowed.isOverloaded) shadowed.alternatives.exists(checkShadowedSymbol)
else checkShadowedSymbol(shadowed)
}
// warn if any checkable pattern var shadows, in the context of the given selector
// warn if any checkable pattern var shadows, in the context of the selector,
// or for `tree match case Apply(fun, args) =>` check whether names in args equal names of fun.params
def check(p: Tree, selSym: Symbol): Unit = {
val traverser = new Traverser {
def checkPattern(p: Tree): Unit = {
val traverser = new InternalTraverser {
// names absolved of shadowing because it is a "current" parameter (of a case class, etc)
var absolved: List[Name] = Nil
override def traverse(t: Tree): Unit = t match {
Expand All @@ -2079,7 +2099,7 @@ abstract class RefChecks extends Transform {
try traverse(arg)
finally absolved = absolved.tail
}
case _ => super.traverse(t)
case _ => t.traverse(this)
}
case bind @ Bind(name, _) =>
def richLocation(sym: Symbol): String = sym.ownsString match {
Expand All @@ -2088,35 +2108,17 @@ abstract class RefChecks extends Transform {
}
for (shade <- bind.getAndRemoveAttachment[PatShadowAttachment]) {
val shadowed = shade.shadowed
if (!absolved.contains(name) && !bind.symbol.hasTransOwner(shadowed.accessedOrSelf) && checkShadowed(shadowed)(selSym))
if (!absolved.contains(name) && !bind.symbol.hasTransOwner(shadowed.accessedOrSelf) && checkShadowed(shadowed))
refchecksWarning(bind.pos, s"Name $name is already introduced in an enclosing scope as ${richLocation(shadowed)}. Did you intend to match it using backquoted `$name`?", WarningCategory.OtherShadowing)

}
case _ => super.traverse(t)
case _ => t.traverse(this)
}
}
traverser.traverse(p)
traverser(p)
}
// check the patterns for unfriendly shadowing, patvars bearing PatShadowAttachment
def checkShadowingPatternVars(): Unit =
selector match {
// or for `(x, y) match case p(x, y) =>` check the corresponding args
case treeInfo.Applied(Select(core, nme.apply), _, tupleArgs :: Nil) if isTupleSymbol(core.symbol.companion) =>
for (cdef <- cases)
cdef.pat match {
case Apply(_, args) if sameLength(args, tupleArgs) =>
foreach2(args, tupleArgs)((arg, sel) => check(arg, notNull(sel.symbol)))
case UnApply(_, args) if sameLength(args, tupleArgs) =>
foreach2(args, tupleArgs)((arg, sel) => check(arg, notNull(sel.symbol)))
case p => check(p, selectorSymbol)
}
// or for `tp.dealiased match case tp =>` check `tp` if it looks like a derived value, but not `this.x` or `x.asInstanceOf`
case treeInfo.Applied(Select(core, _), _, _) if core.symbol != null && !core.symbol.isThisSym && selector.tpe <:< core.tpe.widen =>
for (cdef <- cases) check(cdef.pat, core.symbol)
case _ =>
for (cdef <- cases) check(cdef.pat, selectorSymbol)
}
if (settings.warnPatternShadow) checkShadowingPatternVars()
if (settings.warnPatternShadow) for (cdef <- cases) checkPattern(cdef.pat)
tree
case _ => tree
}
Expand Down
19 changes: 5 additions & 14 deletions test/files/neg/t11850.check
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,18 @@ t11850.scala:9: warning: Name x is already introduced in an enclosing scope as v
t11850.scala:28: warning: Name x is already introduced in an enclosing scope as value x in class CT. Did you intend to match it using backquoted `x`?
case x => 1 // warn
^
t11850.scala:118: warning: Name x is already introduced in an enclosing scope as value x at line 117. Did you intend to match it using backquoted `x`?
case Y(x, y) => x+y // only allow 1-1
^
t11850.scala:118: warning: Name y is already introduced in an enclosing scope as value y at line 117. Did you intend to match it using backquoted `y`?
case Y(x, y) => x+y // only allow 1-1
^
t11850.scala:125: warning: Name self is already introduced in an enclosing scope as value self in class Selfie. Did you intend to match it using backquoted `self`?
t11850.scala:135: warning: Name self is already introduced in an enclosing scope as value self in class Selfie. Did you intend to match it using backquoted `self`?
case (x, self) => x
^
t11850.scala:170: warning: Name _1 is already introduced in an enclosing scope as value _1 in class weird but true. Did you intend to match it using backquoted `_1`?
case (_1, 27) => 3 // briefly did not warn as param name
^
t11850.scala:187: warning: Name x is already introduced in an enclosing scope as value x at line 186. Did you intend to match it using backquoted `x`?
t11850.scala:202: warning: Name x is already introduced in an enclosing scope as value x at line 201. Did you intend to match it using backquoted `x`?
case x => x.toString
^
t11850.scala:203: warning: Name c is already introduced in an enclosing scope as value c in class pattern matches occasionally appear in pattern-matching anonymous functions. Did you intend to match it using backquoted `c`?
t11850.scala:224: warning: Name c is already introduced in an enclosing scope as value c in class pattern matches occasionally appear in pattern-matching anonymous functions. Did you intend to match it using backquoted `c`?
def f = c.collect { case c if c.flag => c.toString }
^
t11850.scala:212: warning: Name x is already introduced in an enclosing scope as object x in object is it worth qualifying what kind of term. Did you intend to match it using backquoted `x`?
t11850.scala:233: warning: Name x is already introduced in an enclosing scope as object x in object is it worth qualifying what kind of term. Did you intend to match it using backquoted `x`?
case x => 1
^
error: No warnings can be incurred under -Werror.
9 warnings
6 warnings
1 error
29 changes: 25 additions & 4 deletions test/files/neg/t11850.scala
Original file line number Diff line number Diff line change
Expand Up @@ -104,20 +104,30 @@ object Y { def unapply(p: (Int, Int, Int)): Option[(Int, Int)] = Option(p).map {
class Tupling {
def f(x: Int, y: Int): Int = (x, y) match {
case (42, 27) => 5
case (x, y) => x+y // correspond to tuple arg
case (x, y) => x+y // correspond to tuple arg (or anywhere in selector)
}
def g(x: Some[Int], y: Some[Int]): Int = (x, y) match {
case (Some(42), Some(27)) => 5
case (Some(x), Some(y)) => x+y // correspond to tuple arg but not top level pattern
case (Some(x), Some(y)) => x+y // correspond to tuple arg but not top level pattern (or anywhere in selector)
}
def e(x: Int, y: Int): Int = (x, y) match {
case X(x, y) => x+y // extractor args correspond to tuple args
case X(x, y) => x+y // extractor args correspond to tuple args (or anywhere in selector)
case _ => -1
}
def err(x: Int, y: Int, z: Int): Int = (x, y, z) match {
case Y(x, y) => x+y // only allow 1-1
case Y(x, y) => x+y // only allow 1-1 (or anywhere in selector)
case _ => -1
}
def swap(x: Int, y: Int): Int = (x, y) match {
case X(y, x) => x+y // anywhere in selector
case _ => -1
}
def add1(x: Int): Int = x + 1 match {
case x => x+42 // anywhere in selector
}
def add2(x: Int): Int = 1 + x match {
case x => x+42 // anywhere in selector
}
}
class Selfie { self =>
def f(x: Int, y: Selfie): Int = (x, y) match {
Expand Down Expand Up @@ -183,10 +193,21 @@ class `derived thing is refinement` {
}
}
class `kosher selector` {
def f(x: Any) = x.toString match {
case x => x
}
}
class `unkosher selector` {
def f(x: Any) = 42 match {
case x => x.toString
}
}
class `also unkosher selector` {
// selector is a value derived from x but it is an unrelated type; x does not "refine" Thing
def f(x: Thing) = x.toString match {
case x => x
}
}
class `lukas asked whats that null check for` {
import annotation._
def isOperatorPart(c: Char): Boolean = (c: @unchecked) match {
Expand Down

0 comments on commit e534c44

Please sign in to comment.