Skip to content

Commit 8e71dc2

Browse files
committed
Check nesteded scrutinees for scrutability
1 parent 260fbf2 commit 8e71dc2

File tree

9 files changed

+65
-6
lines changed

9 files changed

+65
-6
lines changed

compiler/src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1158,6 +1158,7 @@ trait Applications extends Compatibility {
11581158
def typedUnApply(tree: untpd.Apply, selType: Type)(using Context): Tree = {
11591159
record("typedUnApply")
11601160
val Apply(qual, args) = tree
1161+
checkScrutable(selType, tree.srcPos, pattern = true)
11611162

11621163
def notAnExtractor(tree: Tree): Tree =
11631164
// prefer inner errors

compiler/src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1246,6 +1246,11 @@ trait Checking {
12461246
if preExisting.exists || seen.contains(tname) then
12471247
report.error(em"@targetName annotation ${'"'}$tname${'"'} clashes with other definition in same scope", stat.srcPos)
12481248
if stat.isDef then seen += tname
1249+
1250+
def checkScrutable(tp: Type, pos: SrcPos, pattern: Boolean)(using Context): Unit =
1251+
if !tp.derivesFrom(defn.ScrutableClass) && sourceVersion.isAtLeast(`3.1-migration`) then
1252+
val kind = if pattern then "pattern selector " else ""
1253+
report.warning(em"${kind}type $tp should not be scrutinized", pos)
12491254
}
12501255

12511256
trait ReChecking extends Checking {
@@ -1256,6 +1261,7 @@ trait ReChecking extends Checking {
12561261
override def checkFullyAppliedType(tree: Tree)(using Context): Unit = ()
12571262
override def checkEnumCaseRefsLegal(cdef: TypeDef, enumCtx: Context)(using Context): Unit = ()
12581263
override def checkAnnotApplicable(annot: Tree, sym: Symbol)(using Context): Boolean = true
1264+
override def checkScrutable(tp: Type, pos: SrcPos, pattern: Boolean)(using Context): Unit = ()
12591265
}
12601266

12611267
trait NoChecking extends ReChecking {

compiler/src/dotty/tools/dotc/typer/ReTyper.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,5 @@ class ReTyper extends Typer with ReChecking {
140140

141141
override protected def addAccessorDefs(cls: Symbol, body: List[Tree])(using Context): List[Tree] = body
142142
override protected def checkEqualityEvidence(tree: tpd.Tree, pt: Type)(using Context): Unit = ()
143-
override protected def checkScrutable(sel: Tree)(using Context): Unit = ()
144143
override protected def matchingApply(methType: MethodOrPoly, pt: FunProto)(using Context): Boolean = true
145144
}

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,7 @@ class Typer extends Namer
764764
def handlePattern: Tree = {
765765
val tpt1 = typedTpt
766766
if (!ctx.isAfterTyper && pt != defn.ImplicitScrutineeTypeRef)
767+
checkScrutable(pt, tree.srcPos, pattern = true)
767768
withMode(Mode.GadtConstraintInference) {
768769
TypeComparer.constrainPatternType(tpt1.tpe, pt)
769770
}
@@ -1294,8 +1295,6 @@ class Typer extends Namer
12941295
assignType(cpy.Closure(tree)(env1, meth1, target), meth1, target)
12951296
}
12961297

1297-
protected def checkScrutable(sel: Tree)(using Context): Unit = adapt(sel, defn.ScrutableType)
1298-
12991298
def typedMatch(tree: untpd.Match, pt: Type)(using Context): Tree =
13001299
tree.selector match {
13011300
case EmptyTree =>
@@ -1317,10 +1316,9 @@ class Typer extends Namer
13171316
typed(desugar.makeCaseLambda(tree.cases, checkMode, protoFormals.length).withSpan(tree.span), pt)
13181317
}
13191318
case _ =>
1319+
if tree.isInline then checkInInlineContext("inline match", tree.srcPos)
13201320
val sel1 = typedExpr(tree.selector)
13211321
val selType = fullyDefinedType(sel1.tpe, "pattern selector", tree.span).widen
1322-
if tree.isInline then checkInInlineContext("inline match", tree.srcPos)
1323-
else checkScrutable(sel1)
13241322

13251323
/** Extractor for match types hidden behind an AppliedType/MatchAlias */
13261324
object MatchTypeInDisguise {
@@ -3531,6 +3529,7 @@ class Typer extends Namer
35313529
case _ => pt.isScrutable
35323530

35333531
if isScrutableTarget && !wtp.derivesFrom(defn.ScrutableClass) then
3532+
checkScrutable(wtp, tree.srcPos, pattern = false)
35343533
val target = AndType(tree.tpe.widenExpr, defn.ScrutableType)
35353534
if target <:< pt then
35363535
return readapt(tree.cast(target))

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ class CompilationTests {
164164
compileFile("tests/neg-custom-args/typeclass-derivation2.scala", defaultOptions.and("-Yerased-terms")),
165165
compileFile("tests/neg-custom-args/i5498-postfixOps.scala", defaultOptions withoutLanguageFeature "postfixOps"),
166166
compileFile("tests/neg-custom-args/deptypes.scala", defaultOptions.and("-language:experimental.dependent")),
167+
compileFile("tests/neg-custom-args/scrutable.scala", defaultOptions.and("-Xfatal-warnings", "-source", "3.1")),
168+
compileFile("tests/neg-custom-args/i7314.scala", defaultOptions.and("-Xfatal-warnings", "-source", "3.1"))
167169
).checkExpectedErrors()
168170
}
169171

tests/neg-custom-args/i7314.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@main def Test =
2+
// conversion out of the opaque type:
3+
val imm1 = IArray(1,2,3) // supposedly immutable
4+
println(imm1(0)) // 1
5+
imm1 match {
6+
case a: Array[Int] => // error: should not be scrutinized
7+
a(0) = 0
8+
}
9+
println(imm1(0)) // 0

tests/neg-custom-args/scrutable.scala

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
def foo[T](x: T): Scrutable =
2+
println(x.getClass()) // error: should not be scrutinized
3+
println(x.isInstanceOf[Int]) // error: should not be scrutinized
4+
x match
5+
case x: Int => // error: should not be scrutinized
6+
println("int")
7+
x
8+
case x: String => // error: should not be scrutinized
9+
println("string")
10+
x
11+
List(x) match
12+
case (x: Int) :: Nil => // error: should not be scrutinized
13+
println("int")
14+
x
15+
case List(x: String) => // error: should not be scrutinized
16+
println("string")
17+
x
18+
case List(y :: Nil) => // error: should not be scrutinized
19+
y :: Nil
20+
case _ =>
21+
x // error: should not be scrutinized
22+
23+
@main def Test =
24+
val x: Scrutable = foo(1)
25+
val y: Scrutable = foo("hello")
26+
assert(x != y)
27+

tests/run/scrutable.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
class java.lang.Integer
2+
true
3+
int
24
int
35
class java.lang.String
6+
false
7+
string
48
string

tests/run/scrutable.scala

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
1-
def foo[T](x: Any): Scrutable =
1+
def foo[T](x: T): Scrutable =
22
println(x.getClass())
3+
println(x.isInstanceOf[Int])
34
x match
45
case x: Int =>
56
println("int")
67
x
78
case x: String =>
89
println("string")
910
x
11+
List(x) match
12+
case (x: Int) :: Nil =>
13+
println("int")
14+
x
15+
case List(x: String) =>
16+
println("string")
17+
x
18+
case List(y :: Nil) =>
19+
y :: Nil
20+
case _ =>
21+
x
1022

1123
@main def Test =
1224
val x: Scrutable = foo(1)

0 commit comments

Comments
 (0)