Skip to content

Commit ed9b95b

Browse files
committed
Merge pull request scala#3276 from som-snytt/issue/6120-spurious-check
SI-6120 Suppress extra warnings
2 parents e94faee + 9b2ce26 commit ed9b95b

File tree

5 files changed

+156
-178
lines changed

5 files changed

+156
-178
lines changed

src/compiler/scala/tools/nsc/typechecker/RefChecks.scala

Lines changed: 152 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -954,162 +954,166 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
954954
case Object_eq | Object_ne | Object_== | Object_!= | Any_== | Any_!= => true
955955
case _ => false
956956
}
957-
def checkSensible(pos: Position, fn: Tree, args: List[Tree]) = fn match {
958-
case Select(qual, name @ (nme.EQ | nme.NE | nme.eq | nme.ne)) if args.length == 1 && isObjectOrAnyComparisonMethod(fn.symbol) =>
959-
// Make sure the 'eq' or 'ne' method is the one in AnyRef.
960-
def isReferenceOp = fn.symbol == Object_eq || fn.symbol == Object_ne
961-
def isNew(tree: Tree) = tree match {
962-
case Function(_, _)
963-
| Apply(Select(New(_), nme.CONSTRUCTOR), _) => true
964-
case _ => false
965-
}
966-
def underlyingClass(tp: Type): Symbol = {
967-
val sym = tp.widen.typeSymbol
968-
if (sym.isAbstractType) underlyingClass(sym.info.bounds.hi)
969-
else sym
970-
}
971-
val actual = underlyingClass(args.head.tpe)
972-
val receiver = underlyingClass(qual.tpe)
973-
def onTrees[T](f: List[Tree] => T) = f(List(qual, args.head))
974-
def onSyms[T](f: List[Symbol] => T) = f(List(receiver, actual))
975-
976-
// @MAT normalize for consistency in error message, otherwise only part is normalized due to use of `typeSymbol`
977-
def typesString = normalizeAll(qual.tpe.widen)+" and "+normalizeAll(args.head.tpe.widen)
978-
979-
/* Symbols which limit the warnings we can issue since they may be value types */
980-
val isMaybeValue = Set[Symbol](AnyClass, AnyRefClass, AnyValClass, ObjectClass, ComparableClass, JavaSerializableClass)
981-
982-
// Whether def equals(other: Any) has known behavior: it is the default
983-
// inherited from java.lang.Object, or it is a synthetically generated
984-
// case equals. TODO - more cases are warnable if the target is a synthetic
985-
// equals.
986-
def isUsingWarnableEquals = {
987-
val m = receiver.info.member(nme.equals_)
988-
((m == Object_equals) || (m == Any_equals) || isMethodCaseEquals(m))
989-
}
990-
def isMethodCaseEquals(m: Symbol) = m.isSynthetic && m.owner.isCase
991-
def isCaseEquals = isMethodCaseEquals(receiver.info.member(nme.equals_))
992-
// Whether this == or != is one of those defined in Any/AnyRef or an overload from elsewhere.
993-
def isUsingDefaultScalaOp = {
994-
val s = fn.symbol
995-
(s == Object_==) || (s == Object_!=) || (s == Any_==) || (s == Any_!=)
996-
}
997-
def haveSubclassRelationship = (actual isSubClass receiver) || (receiver isSubClass actual)
998-
999-
// Whether the operands+operator represent a warnable combo (assuming anyrefs)
1000-
// Looking for comparisons performed with ==/!= in combination with either an
1001-
// equals method inherited from Object or a case class synthetic equals (for
1002-
// which we know the logic.)
1003-
def isWarnable = isReferenceOp || (isUsingDefaultScalaOp && isUsingWarnableEquals)
1004-
def isEitherNullable = (NullTpe <:< receiver.info) || (NullTpe <:< actual.info)
1005-
def isEitherValueClass = actual.isDerivedValueClass || receiver.isDerivedValueClass
1006-
def isBoolean(s: Symbol) = unboxedValueClass(s) == BooleanClass
1007-
def isUnit(s: Symbol) = unboxedValueClass(s) == UnitClass
1008-
def isNumeric(s: Symbol) = isNumericValueClass(unboxedValueClass(s)) || isAnyNumber(s)
1009-
def isScalaNumber(s: Symbol) = s isSubClass ScalaNumberClass
1010-
def isJavaNumber(s: Symbol) = s isSubClass JavaNumberClass
1011-
// includes java.lang.Number if appropriate [SI-5779]
1012-
def isAnyNumber(s: Symbol) = isScalaNumber(s) || isJavaNumber(s)
1013-
def isMaybeAnyValue(s: Symbol) = isPrimitiveValueClass(unboxedValueClass(s)) || isMaybeValue(s)
1014-
// used to short-circuit unrelatedTypes check if both sides are special
1015-
def isSpecial(s: Symbol) = isMaybeAnyValue(s) || isAnyNumber(s)
1016-
val nullCount = onSyms(_ filter (_ == NullClass) size)
1017-
def isNonsenseValueClassCompare = (
1018-
!haveSubclassRelationship
1019-
&& isUsingDefaultScalaOp
1020-
&& isEitherValueClass
1021-
&& !isCaseEquals
1022-
)
957+
/** Check the sensibility of using the given `equals` to compare `qual` and `other`. */
958+
private def checkSensibleEquals(pos: Position, qual: Tree, name: Name, sym: Symbol, other: Tree) = {
959+
def isReferenceOp = sym == Object_eq || sym == Object_ne
960+
def isNew(tree: Tree) = tree match {
961+
case Function(_, _) | Apply(Select(New(_), nme.CONSTRUCTOR), _) => true
962+
case _ => false
963+
}
964+
def underlyingClass(tp: Type): Symbol = {
965+
val sym = tp.widen.typeSymbol
966+
if (sym.isAbstractType) underlyingClass(sym.info.bounds.hi)
967+
else sym
968+
}
969+
val actual = underlyingClass(other.tpe)
970+
val receiver = underlyingClass(qual.tpe)
971+
def onTrees[T](f: List[Tree] => T) = f(List(qual, other))
972+
def onSyms[T](f: List[Symbol] => T) = f(List(receiver, actual))
973+
974+
// @MAT normalize for consistency in error message, otherwise only part is normalized due to use of `typeSymbol`
975+
def typesString = normalizeAll(qual.tpe.widen)+" and "+normalizeAll(other.tpe.widen)
976+
977+
/* Symbols which limit the warnings we can issue since they may be value types */
978+
val isMaybeValue = Set[Symbol](AnyClass, AnyRefClass, AnyValClass, ObjectClass, ComparableClass, JavaSerializableClass)
979+
980+
// Whether def equals(other: Any) has known behavior: it is the default
981+
// inherited from java.lang.Object, or it is a synthetically generated
982+
// case equals. TODO - more cases are warnable if the target is a synthetic
983+
// equals.
984+
def isUsingWarnableEquals = {
985+
val m = receiver.info.member(nme.equals_)
986+
((m == Object_equals) || (m == Any_equals) || isMethodCaseEquals(m))
987+
}
988+
def isMethodCaseEquals(m: Symbol) = m.isSynthetic && m.owner.isCase
989+
def isCaseEquals = isMethodCaseEquals(receiver.info.member(nme.equals_))
990+
// Whether this == or != is one of those defined in Any/AnyRef or an overload from elsewhere.
991+
def isUsingDefaultScalaOp = sym == Object_== || sym == Object_!= || sym == Any_== || sym == Any_!=
992+
def haveSubclassRelationship = (actual isSubClass receiver) || (receiver isSubClass actual)
993+
994+
// Whether the operands+operator represent a warnable combo (assuming anyrefs)
995+
// Looking for comparisons performed with ==/!= in combination with either an
996+
// equals method inherited from Object or a case class synthetic equals (for
997+
// which we know the logic.)
998+
def isWarnable = isReferenceOp || (isUsingDefaultScalaOp && isUsingWarnableEquals)
999+
def isEitherNullable = (NullTpe <:< receiver.info) || (NullTpe <:< actual.info)
1000+
def isEitherValueClass = actual.isDerivedValueClass || receiver.isDerivedValueClass
1001+
def isBoolean(s: Symbol) = unboxedValueClass(s) == BooleanClass
1002+
def isUnit(s: Symbol) = unboxedValueClass(s) == UnitClass
1003+
def isNumeric(s: Symbol) = isNumericValueClass(unboxedValueClass(s)) || isAnyNumber(s)
1004+
def isScalaNumber(s: Symbol) = s isSubClass ScalaNumberClass
1005+
def isJavaNumber(s: Symbol) = s isSubClass JavaNumberClass
1006+
// includes java.lang.Number if appropriate [SI-5779]
1007+
def isAnyNumber(s: Symbol) = isScalaNumber(s) || isJavaNumber(s)
1008+
def isMaybeAnyValue(s: Symbol) = isPrimitiveValueClass(unboxedValueClass(s)) || isMaybeValue(s)
1009+
// used to short-circuit unrelatedTypes check if both sides are special
1010+
def isSpecial(s: Symbol) = isMaybeAnyValue(s) || isAnyNumber(s)
1011+
val nullCount = onSyms(_ filter (_ == NullClass) size)
1012+
def isNonsenseValueClassCompare = (
1013+
!haveSubclassRelationship
1014+
&& isUsingDefaultScalaOp
1015+
&& isEitherValueClass
1016+
&& !isCaseEquals
1017+
)
10231018

1024-
def nonSensibleWarning(what: String, alwaysEqual: Boolean) = {
1025-
val msg = alwaysEqual == (name == nme.EQ || name == nme.eq)
1026-
unit.warning(pos, "comparing "+what+" using `"+name.decode+"' will always yield " + msg)
1027-
}
1028-
def nonSensible(pre: String, alwaysEqual: Boolean) =
1029-
nonSensibleWarning(pre+"values of types "+typesString, alwaysEqual)
1030-
def nonSensiblyEq() = nonSensible("", true)
1031-
def nonSensiblyNeq() = nonSensible("", false)
1032-
def nonSensiblyNew() = nonSensibleWarning("a fresh object", false)
1033-
1034-
def unrelatedMsg = name match {
1035-
case nme.EQ | nme.eq => "never compare equal"
1036-
case _ => "always compare unequal"
1037-
}
1038-
def unrelatedTypes() = {
1039-
val weaselWord = if (isEitherValueClass) "" else " most likely"
1040-
unit.warning(pos, s"$typesString are unrelated: they will$weaselWord $unrelatedMsg")
1041-
}
1019+
// Have we already determined that the comparison is non-sensible? I mean, non-sensical?
1020+
var isNonSensible = false
1021+
1022+
def nonSensibleWarning(what: String, alwaysEqual: Boolean) = {
1023+
val msg = alwaysEqual == (name == nme.EQ || name == nme.eq)
1024+
unit.warning(pos, s"comparing $what using `${name.decode}' will always yield $msg")
1025+
isNonSensible = true
1026+
}
1027+
def nonSensible(pre: String, alwaysEqual: Boolean) =
1028+
nonSensibleWarning(s"${pre}values of types $typesString", alwaysEqual)
1029+
def nonSensiblyEq() = nonSensible("", alwaysEqual = true)
1030+
def nonSensiblyNeq() = nonSensible("", alwaysEqual = false)
1031+
def nonSensiblyNew() = nonSensibleWarning("a fresh object", alwaysEqual = false)
1032+
1033+
def unrelatedMsg = name match {
1034+
case nme.EQ | nme.eq => "never compare equal"
1035+
case _ => "always compare unequal"
1036+
}
1037+
def unrelatedTypes() = if (!isNonSensible) {
1038+
val weaselWord = if (isEitherValueClass) "" else " most likely"
1039+
unit.warning(pos, s"$typesString are unrelated: they will$weaselWord $unrelatedMsg")
1040+
}
10421041

1043-
if (nullCount == 2) // null == null
1042+
if (nullCount == 2) // null == null
1043+
nonSensiblyEq()
1044+
else if (nullCount == 1) {
1045+
if (onSyms(_ exists isPrimitiveValueClass)) // null == 5
1046+
nonSensiblyNeq()
1047+
else if (onTrees( _ exists isNew)) // null == new AnyRef
1048+
nonSensiblyNew()
1049+
}
1050+
else if (isBoolean(receiver)) {
1051+
if (!isBoolean(actual) && !isMaybeValue(actual)) // true == 5
1052+
nonSensiblyNeq()
1053+
}
1054+
else if (isUnit(receiver)) {
1055+
if (isUnit(actual)) // () == ()
10441056
nonSensiblyEq()
1045-
else if (nullCount == 1) {
1046-
if (onSyms(_ exists isPrimitiveValueClass)) // null == 5
1047-
nonSensiblyNeq()
1048-
else if (onTrees( _ exists isNew)) // null == new AnyRef
1049-
nonSensiblyNew()
1050-
}
1051-
else if (isBoolean(receiver)) {
1052-
if (!isBoolean(actual) && !isMaybeValue(actual)) // true == 5
1057+
else if (!isUnit(actual) && !isMaybeValue(actual)) // () == "abc"
1058+
nonSensiblyNeq()
1059+
}
1060+
else if (isNumeric(receiver)) {
1061+
if (!isNumeric(actual))
1062+
if (isUnit(actual) || isBoolean(actual) || !isMaybeValue(actual)) // 5 == "abc"
10531063
nonSensiblyNeq()
1054-
}
1055-
else if (isUnit(receiver)) {
1056-
if (isUnit(actual)) // () == ()
1057-
nonSensiblyEq()
1058-
else if (!isUnit(actual) && !isMaybeValue(actual)) // () == "abc"
1064+
}
1065+
else if (isWarnable && !isCaseEquals) {
1066+
if (isNew(qual)) // new X == y
1067+
nonSensiblyNew()
1068+
else if (isNew(other) && (receiver.isEffectivelyFinal || isReferenceOp)) // object X ; X == new Y
1069+
nonSensiblyNew()
1070+
else if (receiver.isEffectivelyFinal && !(receiver isSubClass actual) && !actual.isRefinementClass) { // object X, Y; X == Y
1071+
if (isEitherNullable)
1072+
nonSensible("non-null ", false)
1073+
else
10591074
nonSensiblyNeq()
10601075
}
1061-
else if (isNumeric(receiver)) {
1062-
if (!isNumeric(actual))
1063-
if (isUnit(actual) || isBoolean(actual) || !isMaybeValue(actual)) // 5 == "abc"
1064-
nonSensiblyNeq()
1076+
}
1077+
1078+
// warn if one but not the other is a derived value class
1079+
// this is especially important to enable transitioning from
1080+
// regular to value classes without silent failures.
1081+
if (isNonsenseValueClassCompare)
1082+
unrelatedTypes()
1083+
// possibleNumericCount is insufficient or this will warn on e.g. Boolean == j.l.Boolean
1084+
else if (isWarnable && nullCount == 0 && !(isSpecial(receiver) && isSpecial(actual))) {
1085+
// better to have lubbed and lost
1086+
def warnIfLubless(): Unit = {
1087+
val common = global.lub(List(actual.tpe, receiver.tpe))
1088+
if (ObjectTpe <:< common)
1089+
unrelatedTypes()
10651090
}
1066-
else if (isWarnable && !isCaseEquals) {
1067-
if (isNew(qual)) // new X == y
1068-
nonSensiblyNew()
1069-
else if (isNew(args.head) && (receiver.isEffectivelyFinal || isReferenceOp)) // object X ; X == new Y
1070-
nonSensiblyNew()
1071-
else if (receiver.isEffectivelyFinal && !(receiver isSubClass actual) && !actual.isRefinementClass) { // object X, Y; X == Y
1072-
if (isEitherNullable)
1073-
nonSensible("non-null ", false)
1074-
else
1075-
nonSensiblyNeq()
1091+
// warn if actual has a case parent that is not same as receiver's;
1092+
// if actual is not a case, then warn if no common supertype, as below
1093+
if (isCaseEquals) {
1094+
def thisCase = receiver.info.member(nme.equals_).owner
1095+
actual.info.baseClasses.find(_.isCase) match {
1096+
case Some(p) if p != thisCase => nonSensible("case class ", false)
1097+
case None =>
1098+
// stronger message on (Some(1) == None)
1099+
//if (receiver.isCase && receiver.isEffectivelyFinal && !(receiver isSubClass actual)) nonSensiblyNeq()
1100+
//else
1101+
// if a class, it must be super to thisCase (and receiver) since not <: thisCase
1102+
if (!actual.isTrait && !(receiver isSubClass actual)) nonSensiblyNeq()
1103+
else if (!haveSubclassRelationship) warnIfLubless()
1104+
case _ =>
10761105
}
10771106
}
1078-
1079-
// warn if one but not the other is a derived value class
1080-
// this is especially important to enable transitioning from
1081-
// regular to value classes without silent failures.
1082-
if (isNonsenseValueClassCompare)
1083-
unrelatedTypes()
1084-
// possibleNumericCount is insufficient or this will warn on e.g. Boolean == j.l.Boolean
1085-
else if (isWarnable && nullCount == 0 && !(isSpecial(receiver) && isSpecial(actual))) {
1086-
// better to have lubbed and lost
1087-
def warnIfLubless(): Unit = {
1088-
val common = global.lub(List(actual.tpe, receiver.tpe))
1089-
if (ObjectTpe <:< common)
1090-
unrelatedTypes()
1091-
}
1092-
// warn if actual has a case parent that is not same as receiver's;
1093-
// if actual is not a case, then warn if no common supertype, as below
1094-
if (isCaseEquals) {
1095-
def thisCase = receiver.info.member(nme.equals_).owner
1096-
actual.info.baseClasses.find(_.isCase) match {
1097-
case Some(p) if p != thisCase => nonSensible("case class ", false)
1098-
case None =>
1099-
// stronger message on (Some(1) == None)
1100-
//if (receiver.isCase && receiver.isEffectivelyFinal && !(receiver isSubClass actual)) nonSensiblyNeq()
1101-
//else
1102-
// if a class, it must be super to thisCase (and receiver) since not <: thisCase
1103-
if (!actual.isTrait && !(receiver isSubClass actual)) nonSensiblyNeq()
1104-
else if (!haveSubclassRelationship) warnIfLubless()
1105-
case _ =>
1106-
}
1107-
}
1108-
// warn only if they have no common supertype below Object
1109-
else if (!haveSubclassRelationship) {
1110-
warnIfLubless()
1111-
}
1107+
// warn only if they have no common supertype below Object
1108+
else if (!haveSubclassRelationship) {
1109+
warnIfLubless()
11121110
}
1111+
}
1112+
}
1113+
/** Sensibility check examines flavors of equals. */
1114+
def checkSensible(pos: Position, fn: Tree, args: List[Tree]) = fn match {
1115+
case Select(qual, name @ (nme.EQ | nme.NE | nme.eq | nme.ne)) if args.length == 1 && isObjectOrAnyComparisonMethod(fn.symbol) =>
1116+
checkSensibleEquals(pos, qual, name, fn.symbol, args.head)
11131117
case _ =>
11141118
}
11151119

@@ -1526,7 +1530,8 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
15261530
transform(qual)
15271531

15281532
case Apply(fn, args) =>
1529-
// sensicality should be subsumed by the unreachability/exhaustivity/irrefutability analyses in the pattern matcher
1533+
// sensicality should be subsumed by the unreachability/exhaustivity/irrefutability
1534+
// analyses in the pattern matcher
15301535
if (!inPattern) {
15311536
checkImplicitViewOptionApply(tree.pos, fn, args)
15321537
checkSensible(tree.pos, fn, args)

test/files/neg/checksensible.check

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,6 @@ checksensible.scala:27: warning: comparing values of types Int and Unit using `=
2828
checksensible.scala:29: warning: comparing values of types Int and String using `==' will always yield false
2929
1 == "abc"
3030
^
31-
checksensible.scala:29: warning: Int and String are unrelated: they will most likely never compare equal
32-
1 == "abc"
33-
^
3431
checksensible.scala:33: warning: comparing values of types Some[Int] and Int using `==' will always yield false
3532
Some(1) == 1 // as above
3633
^
@@ -64,18 +61,12 @@ checksensible.scala:51: warning: comparing values of types Int and Unit using `!
6461
checksensible.scala:52: warning: comparing values of types Int and Symbol using `!=' will always yield true
6562
(1 != 'sym)
6663
^
67-
checksensible.scala:52: warning: Int and Symbol are unrelated: they will most likely always compare unequal
68-
(1 != 'sym)
69-
^
7064
checksensible.scala:58: warning: comparing a fresh object using `==' will always yield false
7165
((x: Int) => x + 1) == null
7266
^
7367
checksensible.scala:59: warning: comparing a fresh object using `==' will always yield false
7468
Bep == ((_: Int) + 1)
7569
^
76-
checksensible.scala:59: warning: Bep.type and Int => Int are unrelated: they will most likely never compare equal
77-
Bep == ((_: Int) + 1)
78-
^
7970
checksensible.scala:61: warning: comparing a fresh object using `==' will always yield false
8071
new Object == new Object
8172
^
@@ -91,9 +82,6 @@ checksensible.scala:66: warning: comparing values of types Int and Null using `=
9182
checksensible.scala:71: warning: comparing values of types Bip and Bop using `==' will always yield false
9283
(x1 == x2)
9384
^
94-
checksensible.scala:71: warning: Bip and Bop are unrelated: they will most likely never compare equal
95-
(x1 == x2)
96-
^
9785
checksensible.scala:81: warning: comparing values of types EqEqRefTest.this.C3 and EqEqRefTest.this.Z1 using `==' will always yield false
9886
c3 == z1
9987
^
@@ -106,12 +94,9 @@ checksensible.scala:83: warning: comparing values of types EqEqRefTest.this.Z1 a
10694
checksensible.scala:84: warning: comparing values of types EqEqRefTest.this.C3 and String using `!=' will always yield true
10795
c3 != "abc"
10896
^
109-
checksensible.scala:84: warning: EqEqRefTest.this.C3 and String are unrelated: they will most likely always compare unequal
110-
c3 != "abc"
111-
^
11297
checksensible.scala:95: warning: comparing values of types Unit and Int using `!=' will always yield true
11398
while ((c = in.read) != -1)
11499
^
115100
error: No warnings can be incurred under -Xfatal-warnings.
116-
38 warnings found
101+
33 warnings found
117102
one error found

0 commit comments

Comments
 (0)