Skip to content

Commit 80bd831

Browse files
committed
Done
1 parent d3182fd commit 80bd831

10 files changed

Lines changed: 64 additions & 31 deletions

File tree

compiler/src/dotty/tools/backend/jvm/opt/OptimizerUtils.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,15 @@ object OptimizerUtils:
107107
}
108108
}
109109

110+
private def isSideEffectFreeListCall(mi: MethodInsnNode): Boolean =
111+
(mi.owner == "scala/collection/immutable/$colon$colon") &&
112+
(mi.name == "next" || mi.name == "head")
113+
110114
def isSideEffectFreeCall(mi: MethodInsnNode): Boolean = {
111115
isScalaBox(mi) || // not Scala unbox, it may CCE
112116
isJavaBox(mi) || // not Java unbox, it may NPE
113117
isSideEffectFreeConstructorCall(mi) ||
118+
isSideEffectFreeListCall(mi) ||
114119
AnalysisUtils.isClassTagApply(mi) ||
115120
isStdLibModuleAlias(mi)
116121
}

compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ object PatternMatcher {
246246
}
247247

248248
/** Plan for matching `scrutinee` symbol against `tree` pattern */
249-
private def patternPlan(scrutinee: Symbol, tree: Tree, onSuccess: Plan, isDefinitelyCatchAll: Boolean): Plan = {
249+
private def patternPlan(scrutinee: Symbol, tree: Tree, onSuccess: Plan, isDefinitelyCatchAll: Boolean, selectorType: Type): Plan = {
250250

251251
extension (tree: Tree) def avoidPatBoundType(): Type =
252252
tree.tpe.widen match
@@ -289,7 +289,7 @@ object PatternMatcher {
289289
ResultPlan(tpd.Throw(tpd.nullLiteral.cast(defn.ThrowableType)))
290290
else
291291
val sym :: syms1 = syms: @unchecked
292-
patternPlan(sym, arg, matchArgsPatternPlan(args1, syms1), isDefinitelyCatchAll)
292+
patternPlan(sym, arg, matchArgsPatternPlan(args1, syms1), isDefinitelyCatchAll, selectorType)
293293
case Nil =>
294294
assert(syms.isEmpty)
295295
onSuccess
@@ -343,7 +343,7 @@ object PatternMatcher {
343343
val matchRemaining =
344344
letAbstract(remaining): remainingResult =>
345345
if trailing.isEmpty then
346-
patternPlan(remainingResult, arg, onSuccess, isDefinitelyCatchAll)
346+
patternPlan(remainingResult, arg, onSuccess, isDefinitelyCatchAll, selectorType)
347347
else
348348
val seq = ref(remainingResult)
349349
.select(defn.Seq_dropRight.matchingMember(remainingResult.info))
@@ -355,7 +355,7 @@ object PatternMatcher {
355355
val matchTrailing =
356356
letAbstract(rest): trailingResult =>
357357
matchElemsPlan(trailingResult, trailing, noLengthTest, onSuccess)
358-
patternPlan(seqResult, arg, matchTrailing, isDefinitelyCatchAll)
358+
patternPlan(seqResult, arg, matchTrailing, isDefinitelyCatchAll, selectorType)
359359
matchElemsPlan(getResult, leading,
360360
LengthTest(leading.length + trailing.length, exact = false),
361361
matchRemaining)
@@ -485,10 +485,17 @@ object PatternMatcher {
485485
else tpt.tpe
486486
val body = letAbstract(ref(scrutinee).cast(castTp)) { casted =>
487487
nonNull += casted
488-
patternPlan(casted, pat, onSuccess, isDefinitelyCatchAll)
488+
patternPlan(casted, pat, onSuccess, isDefinitelyCatchAll, selectorType)
489489
}
490-
if isDefinitelyCatchAll then body
491-
else TestPlan(TypeTest(tpt, isTrusted(pat)), scrutinee, tree.span, body)
490+
val patIsTrusted = isTrusted(pat)
491+
if isDefinitelyCatchAll then
492+
// Because we're not inserting a type test, we need to explicitly check for unchecked type conversions
493+
// e.g., `(x: List[Int] | List[String]) match { case l: List[Int] => ??? }` should warn,
494+
// even if it is a catch-all after erasure.
495+
TypeTestsCasts.checkTypePattern(selectorType, castTp, tree.srcPos, patIsTrusted)
496+
body
497+
else
498+
TestPlan(TypeTest(tpt, patIsTrusted), scrutinee, tree.span, body)
492499
case UnApply(extractor, implicits, args) =>
493500
val unappPlan = if (scrutinee.info.isBottomType)
494501
// Generate a throwaway but type-correct plan.
@@ -513,19 +520,19 @@ object PatternMatcher {
513520
if (scrutinee.info.isNotNull || nonNull(scrutinee)) unappPlan
514521
else TestPlan(NonNullTest, scrutinee, tree.span, unappPlan)
515522
case Bind(name, body) =>
516-
if (name == nme.WILDCARD) patternPlan(scrutinee, body, onSuccess, isDefinitelyCatchAll)
523+
if (name == nme.WILDCARD) patternPlan(scrutinee, body, onSuccess, isDefinitelyCatchAll, selectorType)
517524
else {
518525
// The type of `name` may refer to val in `body`, therefore should come after `body`
519526
val bound = tree.symbol.asTerm
520527
initializer(bound) = ref(scrutinee)
521-
patternPlan(scrutinee, body, LetPlan(bound, onSuccess), isDefinitelyCatchAll)
528+
patternPlan(scrutinee, body, LetPlan(bound, onSuccess), isDefinitelyCatchAll, selectorType)
522529
}
523530
case Alternative(alts) =>
524531
altsLabeledAbstract { onf =>
525532
SeqPlan(
526533
altsLabeledAbstract { ons =>
527534
alts.foldRight(onf) { (alt, next) =>
528-
SeqPlan(patternPlan(scrutinee, alt, ons, isDefinitelyCatchAll), next)
535+
SeqPlan(patternPlan(scrutinee, alt, ons, isDefinitelyCatchAll, selectorType), next)
529536
}
530537
},
531538
onSuccess
@@ -542,34 +549,35 @@ object PatternMatcher {
542549
case SeqLiteral(pats, _) =>
543550
matchElemsPlan(scrutinee, pats, LengthTest(pats.length, exact = true), onSuccess)
544551
case _ =>
545-
TestPlan(EqualTest(tree), scrutinee, tree.span, onSuccess)
552+
if isDefinitelyCatchAll then onSuccess
553+
else TestPlan(EqualTest(tree), scrutinee, tree.span, onSuccess)
546554
}
547555
}
548556

549-
private def caseDefPlan(scrutinee: Symbol, cdef: CaseDef, isDefinitelyCatchAll: Boolean): Plan =
557+
private def caseDefPlan(scrutinee: Symbol, cdef: CaseDef, isDefinitelyCatchAll: Boolean, selectorType: Type): Plan =
550558
val CaseDef(pat, guard, body) = cdef
551559
val caseDefBodyPlan: Plan = body match
552560
case t: SubMatch => subMatchPlan(t)
553561
case _ => ResultPlan(body)
554562
val onSuccess: Plan =
555563
if guard.isEmpty then caseDefBodyPlan
556564
else TestPlan(GuardTest, guard, guard.span, caseDefBodyPlan)
557-
patternPlan(scrutinee, pat, onSuccess, isDefinitelyCatchAll)
565+
patternPlan(scrutinee, pat, onSuccess, isDefinitelyCatchAll, selectorType)
558566
end caseDefPlan
559567

560568
// like matchPlan but without a final matchError ResultPlan at the end of SeqPlans
561569
// s.t. we fall back to the outer SeqPlan
562570
private def subMatchPlan(tree: SubMatch): Plan =
563571
letAbstract(tree.selector) { scrutinee =>
564-
tree.cases.map(caseDefPlan(scrutinee, _, false)).reduceRight(SeqPlan(_, _))
572+
tree.cases.map(caseDefPlan(scrutinee, _, false, tree.selector.tpe)).reduceRight(SeqPlan(_, _))
565573
}
566574

567575
private def matchPlan(tree: Match, isExhaustive: Boolean): Plan =
568576
letAbstract(tree.selector) { scrutinee =>
569577
val matchError: Plan = ResultPlan(Throw(New(defn.MatchErrorClass.typeRef, ref(scrutinee) :: Nil)))
570578
var isCatchAll = isExhaustive
571579
tree.cases.foldRight(matchError) { (cdef, next) =>
572-
val casePlan = SeqPlan(caseDefPlan(scrutinee, cdef, isCatchAll), next)
580+
val casePlan = SeqPlan(caseDefPlan(scrutinee, cdef, isCatchAll, tree.selector.tpe), next)
573581
isCatchAll = false
574582
casePlan
575583
}

compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ object TypeTestsCasts {
387387
def checkBind(tree: Bind)(using Context) =
388388
checkTypePattern(defn.ThrowableType, tree.body.tpe, tree.srcPos)
389389

390-
private def checkTypePattern(exprTpe: Type, castTpe: Type, pos: SrcPos, trustTypeApplication: Boolean = false)(using Context) =
390+
def checkTypePattern(exprTpe: Type, castTpe: Type, pos: SrcPos, trustTypeApplication: Boolean = false)(using Context) =
391391
val isUnchecked = exprTpe.widenTermRefExpr.hasAnnotation(defn.UncheckedAnnot)
392392
if !isUnchecked then
393393
val whyNot = whyUncheckable(exprTpe, castTpe, pos.span, trustTypeApplication)

compiler/test/dotty/tools/backend/jvm/LocalOptimizationBytecodeTests.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,26 @@ class LocalOptimizationBytecodeTests extends OptimizationBytecodeTest {
1717
returnType = "Boolean"
1818
)
1919

20+
// Ensure writing `case None` is as efficient as `case _` after `case Some`
21+
// This matters not only to avoid a null check in itself, but because the function may then be small enough to be inlineable
22+
@Test def patternMatchingOption =
23+
assertEquivalence(
24+
"if x.isInstanceOf[Some[Int]] then x.asInstanceOf[Some[Int]].value else 0",
25+
"x match { case Some(v) => v; case None => 0 }",
26+
params = List("x: Option[Int]"),
27+
returnType = "Int"
28+
)
29+
30+
// Same but for List: `case Nil` should be as efficient as `case _` after `case _ :: _`
31+
@Test def patternMatchingList =
32+
assertEquivalence(
33+
"if x.isInstanceOf[`::`[Int]] then x.asInstanceOf[`::`[Int]].head else 0",
34+
"x match { case v :: _ => v; case Nil => 0 }",
35+
params = List("x: List[Int]"),
36+
returnType = "Int"
37+
)
38+
39+
// Similar as the above, inspired by a compiler function
2040
@Test def patternMatchingLazyOfT =
2141
assertEquivalence(
2242
"if x.isInstanceOf[Lazy[?]] then x.asInstanceOf[Lazy[T]].value else x.asInstanceOf[T]",

compiler/test/dotty/tools/dotc/Playground.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import org.junit.Ignore
1515
// can add, e.g., .and("-some-option")
1616
val options = defaultOptions
1717
// can also use `compileDir` (single test as a dir), `compileFilesInDir` (all tests within a dir)
18-
val test = compileFile("tests/warn/i5826.min.scala", options)
18+
val test = compileFile("tests/pos/tuple-filter.scala", options)
1919
// or `RunTestWithCoverage` for "run" tests with output, or `WarnTestWithCoverage` for "warn" tests with warnings
2020
type TestKind = PosTestWithCoverage
2121
val compilationTest = withCoverage(aggregateTests(test))

tests/patmat/t11620.check

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
-- [E092] Pattern Match Unchecked Warning: tests/patmat/t11620.scala:33:13 -----
2+
33 | case B.Aux(a: A2[T]) => a.t1 // 👎 (false-positive): unreachable code
3+
| ^^^^^^^^
4+
|the type test for A2[T] cannot be checked at runtime because its type arguments can't be determined from B.Aux[_A, T]
5+
|
6+
| longer explanation available when compiling with `-explain`
17
-- [E029] Pattern Match Exhaustivity Warning: tests/patmat/t11620.scala:36:23 --
28
36 |def foo4[T](b: B[T]) = b match {
39
| ^
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
//> using options -Werror
2-
31
sealed trait Unset
42

53
def foo(v: Unset|Option[Int]): Unit = v match
64
case v: Unset => ()
7-
case v: Option[Int] => ()
5+
case v: Option[Int] => () // ok

tests/warn/i25100.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ def main(): Unit = {
1313
val i: Foo | Bar.type = Foo("Foo")
1414

1515
i match {
16+
case Bar => println("i is a Bar")
1617
case i: Foo => println("i is a Foo")
17-
case Bar => println("i is a Long")
1818
}
1919
}
2020

tests/warn/i5826.check

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1+
-- [E092] Pattern Match Unchecked Warning: tests/warn/i5826.scala:8:9 --------------------------------------------------
2+
8 | case ls: List[Int] => ls.head // warn: unchecked
3+
| ^^^^^^^^^^^^^
4+
|the type test for List[Int] cannot be checked at runtime because its type arguments can't be determined from List[String]
5+
|
6+
| longer explanation available when compiling with `-explain`
17
-- [E092] Pattern Match Unchecked Warning: tests/warn/i5826.scala:3:9 --------------------------------------------------
28
3 | case ls: List[Int] => ls.head // warn: unchecked
39
| ^
410
| the type test for List[Int] cannot be checked at runtime because its type arguments can't be determined from A
511
|
612
| longer explanation available when compiling with `-explain`
7-
-- [E092] Pattern Match Unchecked Warning: tests/warn/i5826.scala:8:9 --------------------------------------------------
8-
8 | case ls: List[Int] => ls.head // warn: unchecked
9-
| ^
10-
|the type test for List[Int] cannot be checked at runtime because its type arguments can't be determined from List[String]
11-
|
12-
| longer explanation available when compiling with `-explain`
1313
-- [E092] Pattern Match Unchecked Warning: tests/warn/i5826.scala:16:9 -------------------------------------------------
1414
16 | case ls: A[X] => 4 // warn
1515
| ^

tests/warn/i5826.min.scala

Lines changed: 0 additions & 4 deletions
This file was deleted.

0 commit comments

Comments
 (0)