Skip to content

Commit bbd092b

Browse files
authored
Merge pull request #10793 from dotty-staging/fix-10614
Fix #10614: align unchecked pattern definition syntax with Scala 2
2 parents 9e80101 + 95d394a commit bbd092b

File tree

15 files changed

+117
-102
lines changed

15 files changed

+117
-102
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,15 +1006,21 @@ object desugar {
10061006
* - None : sel @unchecked
10071007
* - Exhaustive : sel
10081008
* - IrrefutablePatDef,
1009-
* IrrefutableGenFrom: sel @unchecked with attachment `CheckIrrefutable -> checkMode`
1009+
* IrrefutableGenFrom: sel with attachment `CheckIrrefutable -> checkMode`
10101010
*/
10111011
def makeSelector(sel: Tree, checkMode: MatchCheck)(using Context): Tree =
1012-
if (checkMode == MatchCheck.Exhaustive) sel
1013-
else {
1014-
val sel1 = Annotated(sel, New(ref(defn.UncheckedAnnot.typeRef)))
1015-
if (checkMode != MatchCheck.None) sel1.pushAttachment(CheckIrrefutable, checkMode)
1016-
sel1
1017-
}
1012+
checkMode match
1013+
case MatchCheck.None =>
1014+
Annotated(sel, New(ref(defn.UncheckedAnnot.typeRef)))
1015+
1016+
case MatchCheck.Exhaustive =>
1017+
sel
1018+
1019+
case MatchCheck.IrrefutablePatDef | MatchCheck.IrrefutableGenFrom =>
1020+
// TODO: use `pushAttachment` and investigate duplicate attachment
1021+
sel.withAttachment(CheckIrrefutable, checkMode)
1022+
sel
1023+
end match
10181024

10191025
/** If `pat` is a variable pattern,
10201026
*

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3187,7 +3187,7 @@ object Parsers {
31873187
}
31883188

31893189
/** PatDef ::= ids [‘:’ Type] ‘=’ Expr
3190-
* | Pattern2 [‘:’ Type | Ascription] ‘=’ Expr
3190+
* | Pattern2 [‘:’ Type] ‘=’ Expr
31913191
* VarDef ::= PatDef | id {`,' id} `:' Type `=' `_'
31923192
* ValDcl ::= id {`,' id} `:' Type
31933193
* VarDcl ::= id {`,' id} `:' Type
@@ -3205,11 +3205,7 @@ object Parsers {
32053205
val tpt =
32063206
if (in.token == COLON) {
32073207
in.nextToken()
3208-
if (in.token == AT && lhs.tail.isEmpty) {
3209-
lhs = ascription(first, Location.ElseWhere) :: Nil
3210-
emptyType
3211-
}
3212-
else toplevelTyp()
3208+
toplevelTyp()
32133209
}
32143210
else emptyType
32153211
val rhs =
@@ -3232,9 +3228,13 @@ object Parsers {
32323228
case IdPattern(id, t) => t.isEmpty
32333229
case _ => false
32343230
}
3235-
if rhs.isEmpty && !isAllIds then
3236-
syntaxError(ExpectedTokenButFound(EQUALS, in.token), Span(in.lastOffset))
3237-
PatDef(mods, lhs, tpt, rhs)
3231+
val rhs2 =
3232+
if rhs.isEmpty && !isAllIds then
3233+
syntaxError(ExpectedTokenButFound(EQUALS, in.token), Span(in.lastOffset))
3234+
errorTermTree
3235+
else
3236+
rhs
3237+
PatDef(mods, lhs, tpt, rhs2)
32383238
}
32393239
}
32403240

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ object ContextFunctionResults:
4646
def contextResultCount(sym: Symbol)(using Context): Int =
4747
sym.getAnnotation(defn.ContextResultCountAnnot) match
4848
case Some(annot) =>
49-
val ast.Trees.Literal(Constant(crCount: Int)) :: Nil: @unchecked = annot.arguments
49+
val ast.Trees.Literal(Constant(crCount: Int)) :: Nil = annot.arguments: @unchecked
5050
crCount
5151
case none => 0
5252

@@ -73,7 +73,7 @@ object ContextFunctionResults:
7373
def contextParamCount(tp: Type, crCount: Int): Int =
7474
if crCount == 0 then 0
7575
else
76-
val defn.ContextFunctionType(params, resTpe, isErased): @unchecked = tp
76+
val defn.ContextFunctionType(params, resTpe, isErased) = tp: @unchecked
7777
val rest = contextParamCount(resTpe, crCount - 1)
7878
if isErased then rest else params.length + rest
7979

@@ -106,7 +106,7 @@ object ContextFunctionResults:
106106
def missingCR(tp: Type, crCount: Int): (Type, Int) =
107107
if crCount == 0 then (tp, 0)
108108
else
109-
val defn.ContextFunctionType(formals, resTpe, isErased): @unchecked = tp
109+
val defn.ContextFunctionType(formals, resTpe, isErased) = tp: @unchecked
110110
val result @ (rt, nparams) = missingCR(resTpe, crCount - 1)
111111
assert(nparams <= paramCount)
112112
if nparams == paramCount || isErased then result

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -490,15 +490,15 @@ object Erasure {
490490
def abstracted(args: List[Tree], tp: Type, pt: Type)(using Context): Tree =
491491
if args.length < targetLength then
492492
try
493-
val defn.ContextFunctionType(argTpes, resTpe, isErased): @unchecked = tp
493+
val defn.ContextFunctionType(argTpes, resTpe, isErased) = tp: @unchecked
494494
if isErased then abstracted(args, resTpe, pt)
495495
else
496496
val anonFun = newSymbol(
497497
ctx.owner, nme.ANON_FUN, Flags.Synthetic | Flags.Method,
498498
MethodType(argTpes, resTpe), coord = tree.span.endPos)
499499
anonFun.info = transformInfo(anonFun, anonFun.info)
500500
def lambdaBody(refss: List[List[Tree]]) =
501-
val refs :: Nil : @unchecked = refss
501+
val refs :: Nil = refss: @unchecked
502502
val expandedRefs = refs.map(_.withSpan(tree.span.endPos)) match
503503
case (bunchedParam @ Ident(nme.ALLARGS)) :: Nil =>
504504
argTpes.indices.toList.map(n =>

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

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -671,30 +671,32 @@ trait Checking {
671671
report.error(ex"$cls cannot be instantiated since it${rstatus.msg}", pos)
672672
}
673673

674-
/** Check that pattern `pat` is irrefutable for scrutinee tye `pt`.
675-
* This means `pat` is either marked @unchecked or `pt` conforms to the
674+
/** Check that pattern `pat` is irrefutable for scrutinee type `sel.tpe`.
675+
* This means `sel` is either marked @unchecked or `sel.tpe` conforms to the
676676
* pattern's type. If pattern is an UnApply, do the check recursively.
677677
*/
678-
def checkIrrefutable(pat: Tree, pt: Type, isPatDef: Boolean)(using Context): Boolean = {
678+
def checkIrrefutable(sel: Tree, pat: Tree, isPatDef: Boolean)(using Context): Boolean = {
679+
val pt = sel.tpe
679680

680681
def fail(pat: Tree, pt: Type): Boolean = {
681682
var reportedPt = pt.dropAnnot(defn.UncheckedAnnot)
682683
if (!pat.tpe.isSingleton) reportedPt = reportedPt.widen
683684
val problem = if (pat.tpe <:< reportedPt) "is more specialized than" else "does not match"
684-
val fix = if (isPatDef) "`: @unchecked` after" else "`case ` before"
685-
report.errorOrMigrationWarning(
685+
val fix = if (isPatDef) "adding `: @unchecked` after the expression" else "writing `case ` before the full pattern"
686+
val pos = if (isPatDef) sel.srcPos else pat.srcPos
687+
report.warning(
686688
ex"""pattern's type ${pat.tpe} $problem the right hand side expression's type $reportedPt
687689
|
688-
|If the narrowing is intentional, this can be communicated by writing $fix the full pattern.${err.rewriteNotice}""",
689-
pat.srcPos)
690+
|If the narrowing is intentional, this can be communicated by $fix.${err.rewriteNotice}""",
691+
pos)
690692
false
691693
}
692694

693695
def check(pat: Tree, pt: Type): Boolean = (pt <:< pat.tpe) || fail(pat, pt)
694696

695697
def recur(pat: Tree, pt: Type): Boolean =
696698
!sourceVersion.isAtLeast(`3.1`) || // only for 3.1 for now since mitigations work only after this PR
697-
pat.tpe.widen.hasAnnotation(defn.UncheckedAnnot) || {
699+
pt.hasAnnotation(defn.UncheckedAnnot) || {
698700
patmatch.println(i"check irrefutable $pat: ${pat.tpe} against $pt")
699701
pat match {
700702
case Bind(_, pat1) =>

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

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1386,18 +1386,27 @@ class Typer extends Namer
13861386
}
13871387

13881388
result match {
1389-
case Match(sel, CaseDef(pat, _, _) :: _) =>
1389+
case result @ Match(sel, CaseDef(pat, _, _) :: _) =>
13901390
tree.selector.removeAttachment(desugar.CheckIrrefutable) match {
13911391
case Some(checkMode) =>
13921392
val isPatDef = checkMode == desugar.MatchCheck.IrrefutablePatDef
1393-
if (!checkIrrefutable(pat, sel.tpe, isPatDef) && sourceVersion == `3.1-migration`)
1394-
if (isPatDef) patch(Span(pat.span.end), ": @unchecked")
1393+
if (!checkIrrefutable(sel, pat, isPatDef) && sourceVersion == `3.1-migration`)
1394+
if (isPatDef) patch(Span(tree.selector.span.end), ": @unchecked")
13951395
else patch(Span(pat.span.start), "case ")
1396+
1397+
// skip exhaustivity check in later phase
1398+
// TODO: move the check above to patternMatcher phase
1399+
val uncheckedTpe = AnnotatedType(sel.tpe.widen, Annotation(defn.UncheckedAnnot))
1400+
tpd.cpy.Match(result)(
1401+
selector = tpd.Typed(sel, tpd.TypeTree(uncheckedTpe)),
1402+
cases = result.cases
1403+
)
13961404
case _ =>
1405+
result
13971406
}
13981407
case _ =>
1408+
result
13991409
}
1400-
result
14011410
}
14021411

14031412
/** Special typing of Match tree when the expected type is a MatchType,
@@ -1873,7 +1882,7 @@ class Typer extends Namer
18731882
case _ =>
18741883
var name = tree.name
18751884
if (name == nme.WILDCARD && tree.mods.is(Given)) {
1876-
val Typed(_, tpt): @unchecked = tree.body
1885+
val Typed(_, tpt) = tree.body: @unchecked
18771886
name = desugar.inventGivenOrExtensionName(tpt)
18781887
}
18791888
if (name == nme.WILDCARD) body1

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ class CompilationTests {
118118
aggregateTests(
119119
compileFilesInDir("tests/neg", defaultOptions),
120120
compileFilesInDir("tests/neg-tailcall", defaultOptions),
121-
compileFilesInDir("tests/neg-strict", defaultOptions.and("-source", "3.1")),
121+
compileFilesInDir("tests/neg-strict", defaultOptions.and("-source", "3.1", "-Xfatal-warnings")),
122122
compileFilesInDir("tests/neg-no-kind-polymorphism", defaultOptions and "-Yno-kind-polymorphism"),
123123
compileFilesInDir("tests/neg-custom-args/deprecation", defaultOptions.and("-Xfatal-warnings", "-deprecation")),
124124
compileFilesInDir("tests/neg-custom-args/fatal-warnings", defaultOptions.and("-Xfatal-warnings")),

docs/docs/internals/syntax.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ Def ::= ‘val’ PatDef
374374
| ‘type’ {nl} TypeDcl
375375
| TmplDef
376376
PatDef ::= ids [‘:’ Type] ‘=’ Expr
377-
| Pattern2 [‘:’ Type | Ascription] ‘=’ Expr PatDef(_, pats, tpe?, expr)
377+
| Pattern2 [‘:’ Type] ‘=’ Expr PatDef(_, pats, tpe?, expr)
378378
VarDef ::= PatDef
379379
| ids ‘:’ Type ‘=’ ‘_’
380380
DefDef ::= DefSig [‘:’ Type] ‘=’ Expr DefDef(_, name, tparams, vparamss, tpe, expr)

docs/docs/reference/changed-features/pattern-bindings.md

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ title: "Pattern Bindings"
66
In Scala 2, pattern bindings in `val` definitions and `for` expressions are
77
loosely typed. Potentially failing matches are still accepted at compile-time,
88
but may influence the program's runtime behavior.
9-
From Scala 3.1 on, type checking rules will be tightened so that errors are reported at compile-time instead.
9+
From Scala 3.1 on, type checking rules will be tightened so that warnings are reported at compile-time instead.
1010

1111
## Bindings in Pattern Definitions
1212

@@ -15,7 +15,7 @@ val xs: List[Any] = List(1, 2, 3)
1515
val (x: String) :: _ = xs // error: pattern's type String is more specialized
1616
// than the right hand side expression's type Any
1717
```
18-
This code gives a compile-time error in Scala 3.1 (and also in Scala 3.0 under the `-source 3.1` setting) whereas it will fail at runtime with a `ClassCastException` in Scala 2. In Scala 3.1, a pattern binding is only allowed if the pattern is _irrefutable_, that is, if the right-hand side's type conforms to the pattern's type. For instance, the following is OK:
18+
This code gives a compile-time warning in Scala 3.1 (and also in Scala 3.0 under the `-source 3.1` setting) whereas it will fail at runtime with a `ClassCastException` in Scala 2. In Scala 3.1, a pattern binding is only allowed if the pattern is _irrefutable_, that is, if the right-hand side's type conforms to the pattern's type. For instance, the following is OK:
1919
```scala
2020
val pair = (1, true)
2121
val (x, y) = pair
@@ -25,9 +25,9 @@ want to decompose it like this:
2525
```scala
2626
val first :: rest = elems // error
2727
```
28-
This works in Scala 2. In fact it is a typical use case for Scala 2's rules. But in Scala 3.1 it will give a type error. One can avoid the error by marking the pattern with an `@unchecked` annotation:
28+
This works in Scala 2. In fact it is a typical use case for Scala 2's rules. But in Scala 3.1 it will give a warning. One can avoid the warning by marking the right-hand side with an `@unchecked` annotation:
2929
```scala
30-
val first :: rest : @unchecked = elems // OK
30+
val first :: rest = elems: @unchecked // OK
3131
```
3232
This will make the compiler accept the pattern binding. It might give an error at runtime instead, if the underlying assumption that `elems` can never be empty is wrong.
3333

@@ -40,7 +40,7 @@ val elems: List[Any] = List((1, 2), "hello", (3, 4))
4040
for ((x, y) <- elems) yield (y, x) // error: pattern's type (Any, Any) is more specialized
4141
// than the right hand side expression's type Any
4242
```
43-
This code gives a compile-time error in Scala 3.1 whereas in Scala 2 the list `elems`
43+
This code gives a compile-time warning in Scala 3.1 whereas in Scala 2 the list `elems`
4444
is filtered to retain only the elements of tuple type that match the pattern `(x, y)`.
4545
The filtering functionality can be obtained in Scala 3 by prefixing the pattern with `case`:
4646
```scala
@@ -49,13 +49,11 @@ The filtering functionality can be obtained in Scala 3 by prefixing the pattern
4949

5050
## Syntax Changes
5151

52-
There are two syntax changes relative to Scala 2: First, pattern definitions can carry ascriptions such as `: @unchecked`. Second, generators in for expressions may be prefixed with `case`.
52+
Generators in for expressions may be prefixed with `case`.
5353
```
54-
PatDef ::= ids [‘:’ Type] ‘=’ Expr
55-
| Pattern2 [‘:’ Type | Ascription] ‘=’ Expr
5654
Generator ::= [‘case’] Pattern1 ‘<-’ Expr
5755
```
5856

5957
## Migration
6058

61-
The new syntax is supported in Dotty and Scala 3.0. However, to enable smooth cross compilation between Scala 2 and Scala 3, the changed behavior and additional type checks are only enabled under the `-source 3.1` setting. They will be enabled by default in version 3.1 of the language.
59+
The new syntax is supported in Scala 3.0. However, to enable smooth cross compilation between Scala 2 and Scala 3, the changed behavior and additional type checks are only enabled under the `-source 3.1` setting. They will be enabled by default in version 3.1 of the language.

docs/docs/reference/syntax.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ Def ::= ‘val’ PatDef
361361
| ‘type’ {nl} TypeDcl
362362
| TmplDef
363363
PatDef ::= ids [‘:’ Type] ‘=’ Expr
364-
| Pattern2 [‘:’ Type | Ascription] ‘=’ Expr
364+
| Pattern2 [‘:’ Type] ‘=’ Expr
365365
VarDef ::= PatDef
366366
| ids ‘:’ Type ‘=’ ‘_’
367367
DefDef ::= DefSig [‘:’ Type] ‘=’ Expr

tests/neg-custom-args/infix.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def test() = {
5858
val Pair(_, _) = p
5959
val _ Pair _ = p // error
6060
val _ `Pair` _ = p // OK
61-
val (_ PP _): @unchecked = p // OK
61+
val (_ PP _) = p: @unchecked // OK
6262

6363
val q = Q(1, 2)
6464
val Q(_, _) = q

tests/neg-strict/filtering-fors.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
object Test {
22

3-
val xs: List[Any] = ???
3+
val xs: List[AnyRef] = ???
44

55
for (x <- xs) do () // OK
66
for (x: Any <- xs) do () // OK
@@ -16,7 +16,7 @@ object Test {
1616
for (case (x: String) <- xs; (y, z) <- xs) do () // error
1717
for ((x: String) <- xs; case (y, z) <- xs) do () // error
1818

19-
val pairs: List[Any] = List((1, 2), "hello", (3, 4))
19+
val pairs: List[AnyRef] = List((1, 2), "hello", (3, 4))
2020
for ((x, y) <- pairs) yield (y, x) // error
2121

2222
for (case x: String <- xs) do () // OK

0 commit comments

Comments
 (0)