Skip to content

Commit cc4ca79

Browse files
committed
Fix #10614: align unchecked pattern definition syntax with Scala 2
1 parent 1ec8041 commit cc4ca79

File tree

9 files changed

+45
-37
lines changed

9 files changed

+45
-37
lines changed

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,14 +1006,17 @@ 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 =
10121012
if (checkMode == MatchCheck.Exhaustive) sel
10131013
else {
1014-
val sel1 = Annotated(sel, New(ref(defn.UncheckedAnnot.typeRef)))
1015-
if (checkMode != MatchCheck.None) sel1.pushAttachment(CheckIrrefutable, checkMode)
1016-
sel1
1014+
if (checkMode == MatchCheck.None)
1015+
Annotated(sel, New(ref(defn.UncheckedAnnot.typeRef)))
1016+
else
1017+
// TODO: use `pushAttachment` and investigate duplicate attachment
1018+
sel.withAttachment(CheckIrrefutable, checkMode)
1019+
sel
10171020
}
10181021

10191022
/** If `pat` is a variable pattern,

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3184,7 +3184,7 @@ object Parsers {
31843184
}
31853185

31863186
/** PatDef ::= ids [‘:’ Type] ‘=’ Expr
3187-
* | Pattern2 [‘:’ Type | Ascription] ‘=’ Expr
3187+
* | Pattern2 [‘:’ Type] ‘=’ Expr
31883188
* VarDef ::= PatDef | id {`,' id} `:' Type `=' `_'
31893189
* ValDcl ::= id {`,' id} `:' Type
31903190
* VarDcl ::= id {`,' id} `:' Type
@@ -3202,11 +3202,7 @@ object Parsers {
32023202
val tpt =
32033203
if (in.token == COLON) {
32043204
in.nextToken()
3205-
if (in.token == AT && lhs.tail.isEmpty) {
3206-
lhs = ascription(first, Location.ElseWhere) :: Nil
3207-
emptyType
3208-
}
3209-
else toplevelTyp()
3205+
toplevelTyp()
32103206
}
32113207
else emptyType
32123208
val rhs =

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

Lines changed: 9 additions & 7 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 tye `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+
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
685687
report.errorOrMigrationWarning(
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.widen.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: 13 additions & 4 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, 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,

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: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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 type error. One can avoid the error by marking the rhs 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

@@ -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-strict/unchecked-patterns.scala

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

3-
val (y1: Some[Int] @unchecked) = Some(1): Option[Int] // OK
3+
val (y1: Some[Int]) = Some(1): Option[Int] @unchecked // OK
44
val y2: Some[Int] @unchecked = Some(1): Option[Int] // error
55

66
val x :: xs = List(1, 2, 3) // error
77
val (1, c) = (1, 2) // error
88
val 1 *: cs = 1 *: Tuple() // error
99

10-
val (_: Int | _: Any) = ??? : Any // error
10+
val (_: Int | _: AnyRef) = ??? : AnyRef // error
1111

1212
val 1 = 2 // error
1313

tests/run/unchecked-patterns.scala

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
object Test extends App {
2-
val x: Int @unchecked = 2
3-
val (y1: Some[Int] @unchecked) = Some(1): Option[Int]
2+
val x: Int = 2: @unchecked
3+
val (y1: Some[Int]) = Some(1): Option[Int]: @unchecked
44

5-
val a :: as: @unchecked = List(1, 2, 3)
6-
val lst @ b :: bs: @unchecked = List(1, 2, 3)
7-
val (1, c): @unchecked = (1, 2)
5+
val a :: as = List(1, 2, 3): @unchecked
6+
val lst @ b :: bs = List(1, 2, 3): @unchecked
7+
val (1, c) = (1, 2): @unchecked
88

99
object Positive { def unapply(i: Int): Option[Int] = Some(i).filter(_ > 0) }
10-
val Positive(p): @unchecked = 5
10+
val Positive(p) = 5: @unchecked
1111
}

0 commit comments

Comments
 (0)