Skip to content

Commit d1984ea

Browse files
committed
Retry sealed traits for relatedness
1 parent e7def07 commit d1984ea

File tree

6 files changed

+70
-24
lines changed

6 files changed

+70
-24
lines changed

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

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ trait Checkable {
7676

7777
import global._
7878
import definitions._
79-
import CheckabilityChecker.{ isNeverSubType, isNeverSubClass }
8079

8180
/** The applied type of class 'to' after inferring anything
8281
* possible from the knowledge that 'to' must also be of the
@@ -155,7 +154,7 @@ trait Checkable {
155154
scrut <:< pattTpWild
156155
}
157156

158-
private class CheckabilityChecker(val X: Type, val P: Type) {
157+
private class CheckabilityChecker(val X: Type, val P: Type, isRecheck: Boolean = false) {
159158
def Xsym = X.typeSymbol
160159
def Psym = P.typeSymbol
161160
def PErased = {
@@ -166,7 +165,6 @@ trait Checkable {
166165
}
167166
def XR = if (Xsym == AnyClass) PErased else propagateKnownTypes(X, Psym)
168167

169-
170168
// sadly the spec says (new java.lang.Boolean(true)).isInstanceOf[scala.Boolean]
171169
def P1 = scrutConformsToPatternType(X, P)
172170
def P2 = !Psym.isPrimitiveValueClass && isNeverSubType(X, P)
@@ -215,11 +213,7 @@ trait Checkable {
215213
case TypeRef(_, sym, _) if sym.isAbstractType => "abstract type " + sym.name
216214
case tp => "non-variable type argument " + tp
217215
}
218-
}
219216

220-
/** X, P, [P1], etc. are all explained at the top of the file.
221-
*/
222-
private object CheckabilityChecker {
223217
/** Are these symbols classes with no subclass relationship? */
224218
def areUnrelatedClasses(sym1: Symbol, sym2: Symbol) = (
225219
sym1.isClass
@@ -242,23 +236,21 @@ trait Checkable {
242236
* - neither A nor B is a trait (i.e. both are actual classes, not eligible for mixin)
243237
* - both A and B are sealed/final, and every possible pairing of their children is irreconcilable
244238
*
245-
* TODO: the last two conditions of the last possibility (that the symbols are not of
239+
* The last two conditions of the last possibility (that the symbols are not of
246240
* classes being compiled in the current run) are because this currently runs too early,
247241
* and .children returns Nil for sealed classes because their children will not be
248-
* populated until typer. It was too difficult to move things around for the moment,
249-
* so I will consult with moors about the optimal time to be doing this.
242+
* populated until typer. As a workaround, in this case, this check is performed a second
243+
* time at the end of typer. #6537, #12414
250244
*/
251245
def areIrreconcilableAsParents(sym1: Symbol, sym2: Symbol): Boolean = areUnrelatedClasses(sym1, sym2) && (
252246
isEffectivelyFinal(sym1) // initialization important
253247
|| isEffectivelyFinal(sym2)
254248
|| !sym1.isTrait && !sym2.isTrait
255-
|| isSealedOrFinal(sym1) && isSealedOrFinal(sym2) && allChildrenAreIrreconcilable(sym1, sym2) && !currentRun.compiles(sym1) && !currentRun.compiles(sym2)
249+
|| isSealedOrFinal(sym1) && isSealedOrFinal(sym2) && allChildrenAreIrreconcilable(sym1, sym2) && (isRecheck || !currentRun.compiles(sym1) && !currentRun.compiles(sym2))
256250
)
257251
private def isSealedOrFinal(sym: Symbol) = sym.isSealed || sym.isFinal
258-
private def isEffectivelyFinal(sym: Symbol): Boolean = (
259-
// initialization important
260-
sym.initialize.isEffectivelyFinalOrNotOverridden
261-
)
252+
// initialization important
253+
private def isEffectivelyFinal(sym: Symbol): Boolean = sym.initialize.isEffectivelyFinalOrNotOverridden
262254

263255
def isNeverSubClass(sym1: Symbol, sym2: Symbol) = areIrreconcilableAsParents(sym1, sym2)
264256

@@ -278,7 +270,7 @@ trait Checkable {
278270
case _ =>
279271
false
280272
}
281-
// Important to dealias at any entry point (this is the only one at this writing.)
273+
// Important to dealias at any entry point (this is the only one at this writing but cf isNeverSubClass.)
282274
def isNeverSubType(tp1: Type, tp2: Type): Boolean = /*logResult(s"isNeverSubType($tp1, $tp2)")*/((tp1.dealias, tp2.dealias) match {
283275
case (TypeRef(_, sym1, args1), TypeRef(_, sym2, args2)) =>
284276
isNeverSubClass(sym1, sym2) || {
@@ -311,13 +303,11 @@ trait Checkable {
311303
*
312304
* Instead of the canRemedy flag, annotate uncheckable types that have become checkable because of the availability of a class tag?
313305
*/
314-
def checkCheckable(tree: Tree, P0: Type, X0: Type, inPattern: Boolean, canRemedy: Boolean = false): Unit = {
315-
if (uncheckedOk(P0)) return
316-
def where = if (inPattern) "pattern " else ""
317-
318-
if(P0.typeSymbol == SingletonClass)
306+
def checkCheckable(tree: Tree, P0: Type, X0: Type, inPattern: Boolean, canRemedy: Boolean = false): Unit = if (!uncheckedOk(P0)) {
307+
if (P0.typeSymbol == SingletonClass)
319308
context.warning(tree.pos, s"fruitless type test: every non-null value will be a Singleton dynamically", WarningCategory.Other)
320309
else {
310+
def where = if (inPattern) "pattern " else ""
321311
// singleton types not considered here, dealias the pattern for SI-XXXX
322312
val P = P0.dealiasWiden
323313
val X = X0.widen
@@ -341,24 +331,38 @@ trait Checkable {
341331
if (checker.result == RuntimeCheckable)
342332
log(checker.summaryString)
343333

344-
if (checker.neverMatches) {
345-
val addendum = if (checker.neverSubClass) "" else " (but still might match its erasure)"
334+
def neverMatchesWarning(result: CheckabilityChecker) = {
335+
val addendum = if (result.neverSubClass) "" else " (but still might match its erasure)"
346336
context.warning(tree.pos, s"fruitless type test: a value of type $X cannot also be a $PString$addendum", WarningCategory.Other)
347337
}
338+
if (checker.neverMatches)
339+
neverMatchesWarning(checker)
348340
else if (checker.isUncheckable) {
349341
val msg = (
350342
if (checker.uncheckableType =:= P) s"abstract type $where$PString"
351343
else s"${checker.uncheckableMessage} in type $where$PString"
352344
)
353345
context.warning(tree.pos, s"$msg is unchecked since it is eliminated by erasure", WarningCategory.Unchecked)
354346
}
347+
else if (checker.result == RuntimeCheckable) {
348+
// register deferred checking for sealed types in current run
349+
@`inline` def Xsym = X.typeSymbol
350+
@`inline` def Psym = P.typeSymbol
351+
@`inline` def isSealedOrFinal(sym: Symbol) = sym.isSealed || sym.isFinal
352+
def recheckFruitless(): Unit = {
353+
val rechecker = new CheckabilityChecker(X, P, isRecheck = true)
354+
if (rechecker.neverMatches) neverMatchesWarning(rechecker)
355+
}
356+
if (isSealedOrFinal(Xsym) && isSealedOrFinal(Psym) && (currentRun.compiles(Xsym) || currentRun.compiles(Psym)))
357+
context.unit.toCheck += (() => recheckFruitless())
358+
}
355359
}
356360
}
357361
}
358362
}
359363
}
360364

361-
private[typechecker] final class Checkability(val value: Int) extends AnyVal { }
365+
private[typechecker] final class Checkability(val value: Int) extends AnyVal
362366
private[typechecker] object Checkability {
363367
val StaticallyTrue = new Checkability(0)
364368
val StaticallyFalse = new Checkability(1)

test/files/neg/t12414.check

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
t12414.scala:12: warning: fruitless type test: a value of type Trait1 cannot also be a Trait2
2+
case y: Trait2 =>
3+
^
4+
error: No warnings can be incurred under -Werror.
5+
1 warning
6+
1 error

test/files/neg/t12414.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// scalac: -Werror
2+
3+
sealed trait Trait1
4+
sealed trait Trait2
5+
6+
class Class1 extends Trait1
7+
class Class2 extends Trait2
8+
9+
object Test extends App {
10+
def test(x: Trait1): Unit =
11+
x match {
12+
case y: Trait2 =>
13+
case _ =>
14+
}
15+
}

test/files/neg/t12414b.check

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
b_2.scala:6: warning: fruitless type test: a value of type Trait1 cannot also be a Trait2
2+
case y: Trait2 =>
3+
^
4+
error: No warnings can be incurred under -Werror.
5+
1 warning
6+
1 error

test/files/neg/t12414b/a_1.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
sealed trait Trait1
3+
sealed trait Trait2
4+
5+
class Class1 extends Trait1
6+
class Class2 extends Trait2

test/files/neg/t12414b/b_2.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// scalac: -Werror
2+
3+
object Test extends App {
4+
def test(x: Trait1): Unit =
5+
x match {
6+
case y: Trait2 =>
7+
case _ =>
8+
}
9+
}

0 commit comments

Comments
 (0)