From cc4ca791864bff6e759534fd389b31d3a6bbd897 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Mon, 14 Dec 2020 22:23:34 +0100 Subject: [PATCH 1/7] Fix #10614: align unchecked pattern definition syntax with Scala 2 --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 11 +++++++---- .../src/dotty/tools/dotc/parsing/Parsers.scala | 8 ++------ .../src/dotty/tools/dotc/typer/Checking.scala | 16 +++++++++------- compiler/src/dotty/tools/dotc/typer/Typer.scala | 17 +++++++++++++---- docs/docs/internals/syntax.md | 2 +- .../changed-features/pattern-bindings.md | 10 ++++------ docs/docs/reference/syntax.md | 2 +- tests/neg-strict/unchecked-patterns.scala | 4 ++-- tests/run/unchecked-patterns.scala | 12 ++++++------ 9 files changed, 45 insertions(+), 37 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index e84e0396b5da..6c1f9fb2895f 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1006,14 +1006,17 @@ object desugar { * - None : sel @unchecked * - Exhaustive : sel * - IrrefutablePatDef, - * IrrefutableGenFrom: sel @unchecked with attachment `CheckIrrefutable -> checkMode` + * IrrefutableGenFrom: sel with attachment `CheckIrrefutable -> checkMode` */ def makeSelector(sel: Tree, checkMode: MatchCheck)(using Context): Tree = if (checkMode == MatchCheck.Exhaustive) sel else { - val sel1 = Annotated(sel, New(ref(defn.UncheckedAnnot.typeRef))) - if (checkMode != MatchCheck.None) sel1.pushAttachment(CheckIrrefutable, checkMode) - sel1 + if (checkMode == MatchCheck.None) + Annotated(sel, New(ref(defn.UncheckedAnnot.typeRef))) + else + // TODO: use `pushAttachment` and investigate duplicate attachment + sel.withAttachment(CheckIrrefutable, checkMode) + sel } /** If `pat` is a variable pattern, diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 5fd6545f1f5d..a76a2e1d6007 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3184,7 +3184,7 @@ object Parsers { } /** PatDef ::= ids [‘:’ Type] ‘=’ Expr - * | Pattern2 [‘:’ Type | Ascription] ‘=’ Expr + * | Pattern2 [‘:’ Type] ‘=’ Expr * VarDef ::= PatDef | id {`,' id} `:' Type `=' `_' * ValDcl ::= id {`,' id} `:' Type * VarDcl ::= id {`,' id} `:' Type @@ -3202,11 +3202,7 @@ object Parsers { val tpt = if (in.token == COLON) { in.nextToken() - if (in.token == AT && lhs.tail.isEmpty) { - lhs = ascription(first, Location.ElseWhere) :: Nil - emptyType - } - else toplevelTyp() + toplevelTyp() } else emptyType val rhs = diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index c0c8edd48b80..0d97ebd30907 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -671,22 +671,24 @@ trait Checking { report.error(ex"$cls cannot be instantiated since it${rstatus.msg}", pos) } - /** Check that pattern `pat` is irrefutable for scrutinee tye `pt`. - * This means `pat` is either marked @unchecked or `pt` conforms to the + /** Check that pattern `pat` is irrefutable for scrutinee tye `sel.tpe`. + * This means `sel` is either marked @unchecked or `sel.tpe` conforms to the * pattern's type. If pattern is an UnApply, do the check recursively. */ - def checkIrrefutable(pat: Tree, pt: Type, isPatDef: Boolean)(using Context): Boolean = { + def checkIrrefutable(sel: Tree, pat: Tree, isPatDef: Boolean)(using Context): Boolean = { + val pt = sel.tpe def fail(pat: Tree, pt: Type): Boolean = { var reportedPt = pt.dropAnnot(defn.UncheckedAnnot) if (!pat.tpe.isSingleton) reportedPt = reportedPt.widen val problem = if (pat.tpe <:< reportedPt) "is more specialized than" else "does not match" - val fix = if (isPatDef) "`: @unchecked` after" else "`case ` before" + val fix = if (isPatDef) "adding `: @unchecked` after the expression" else "writing `case ` before the full pattern" + val pos = if (isPatDef) sel.srcPos else pat.srcPos report.errorOrMigrationWarning( ex"""pattern's type ${pat.tpe} $problem the right hand side expression's type $reportedPt | - |If the narrowing is intentional, this can be communicated by writing $fix the full pattern.${err.rewriteNotice}""", - pat.srcPos) + |If the narrowing is intentional, this can be communicated by $fix.${err.rewriteNotice}""", + pos) false } @@ -694,7 +696,7 @@ trait Checking { def recur(pat: Tree, pt: Type): Boolean = !sourceVersion.isAtLeast(`3.1`) || // only for 3.1 for now since mitigations work only after this PR - pat.tpe.widen.hasAnnotation(defn.UncheckedAnnot) || { + pt.widen.hasAnnotation(defn.UncheckedAnnot) || { patmatch.println(i"check irrefutable $pat: ${pat.tpe} against $pt") pat match { case Bind(_, pat1) => diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 576ab22590e8..01d1ecdbf62b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1386,18 +1386,27 @@ class Typer extends Namer } result match { - case Match(sel, CaseDef(pat, _, _) :: _) => + case result @ Match(sel, CaseDef(pat, _, _) :: _) => tree.selector.removeAttachment(desugar.CheckIrrefutable) match { case Some(checkMode) => val isPatDef = checkMode == desugar.MatchCheck.IrrefutablePatDef - if (!checkIrrefutable(pat, sel.tpe, isPatDef) && sourceVersion == `3.1-migration`) - if (isPatDef) patch(Span(pat.span.end), ": @unchecked") + if (!checkIrrefutable(sel, pat, isPatDef) && sourceVersion == `3.1-migration`) + if (isPatDef) patch(Span(tree.selector.span.end), ": @unchecked") else patch(Span(pat.span.start), "case ") + + // skip exhaustivity check in later phase + // TODO: move the check above to patternMatcher phase + val uncheckedTpe = AnnotatedType(sel.tpe, Annotation(defn.UncheckedAnnot)) + tpd.cpy.Match(result)( + selector = tpd.Typed(sel, tpd.TypeTree(uncheckedTpe)), + cases = result.cases + ) case _ => + result } case _ => + result } - result } /** Special typing of Match tree when the expected type is a MatchType, diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 3132bf30aef1..ccd50f47607b 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -374,7 +374,7 @@ Def ::= ‘val’ PatDef | ‘type’ {nl} TypeDcl | TmplDef PatDef ::= ids [‘:’ Type] ‘=’ Expr - | Pattern2 [‘:’ Type | Ascription] ‘=’ Expr PatDef(_, pats, tpe?, expr) + | Pattern2 [‘:’ Type] ‘=’ Expr PatDef(_, pats, tpe?, expr) VarDef ::= PatDef | ids ‘:’ Type ‘=’ ‘_’ DefDef ::= DefSig [‘:’ Type] ‘=’ Expr DefDef(_, name, tparams, vparamss, tpe, expr) diff --git a/docs/docs/reference/changed-features/pattern-bindings.md b/docs/docs/reference/changed-features/pattern-bindings.md index 65c170717b47..f9cd850f17c1 100644 --- a/docs/docs/reference/changed-features/pattern-bindings.md +++ b/docs/docs/reference/changed-features/pattern-bindings.md @@ -25,9 +25,9 @@ want to decompose it like this: ```scala val first :: rest = elems // error ``` -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: +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: ```scala -val first :: rest : @unchecked = elems // OK +val first :: rest = elems: @unchecked // OK ``` 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. @@ -49,13 +49,11 @@ The filtering functionality can be obtained in Scala 3 by prefixing the pattern ## Syntax Changes -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`. +Generators in for expressions may be prefixed with `case`. ``` -PatDef ::= ids [‘:’ Type] ‘=’ Expr - | Pattern2 [‘:’ Type | Ascription] ‘=’ Expr Generator ::= [‘case’] Pattern1 ‘<-’ Expr ``` ## Migration -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. +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. diff --git a/docs/docs/reference/syntax.md b/docs/docs/reference/syntax.md index c46d150ba836..60ce4ed0b693 100644 --- a/docs/docs/reference/syntax.md +++ b/docs/docs/reference/syntax.md @@ -361,7 +361,7 @@ Def ::= ‘val’ PatDef | ‘type’ {nl} TypeDcl | TmplDef PatDef ::= ids [‘:’ Type] ‘=’ Expr - | Pattern2 [‘:’ Type | Ascription] ‘=’ Expr + | Pattern2 [‘:’ Type] ‘=’ Expr VarDef ::= PatDef | ids ‘:’ Type ‘=’ ‘_’ DefDef ::= DefSig [‘:’ Type] ‘=’ Expr diff --git a/tests/neg-strict/unchecked-patterns.scala b/tests/neg-strict/unchecked-patterns.scala index 8cf797e88029..649972d29c64 100644 --- a/tests/neg-strict/unchecked-patterns.scala +++ b/tests/neg-strict/unchecked-patterns.scala @@ -1,13 +1,13 @@ object Test { - val (y1: Some[Int] @unchecked) = Some(1): Option[Int] // OK + val (y1: Some[Int]) = Some(1): Option[Int] @unchecked // OK val y2: Some[Int] @unchecked = Some(1): Option[Int] // error val x :: xs = List(1, 2, 3) // error val (1, c) = (1, 2) // error val 1 *: cs = 1 *: Tuple() // error - val (_: Int | _: Any) = ??? : Any // error + val (_: Int | _: AnyRef) = ??? : AnyRef // error val 1 = 2 // error diff --git a/tests/run/unchecked-patterns.scala b/tests/run/unchecked-patterns.scala index 07539312d79f..3e614fcbf457 100644 --- a/tests/run/unchecked-patterns.scala +++ b/tests/run/unchecked-patterns.scala @@ -1,11 +1,11 @@ object Test extends App { - val x: Int @unchecked = 2 - val (y1: Some[Int] @unchecked) = Some(1): Option[Int] + val x: Int = 2: @unchecked + val (y1: Some[Int]) = Some(1): Option[Int]: @unchecked - val a :: as: @unchecked = List(1, 2, 3) - val lst @ b :: bs: @unchecked = List(1, 2, 3) - val (1, c): @unchecked = (1, 2) + val a :: as = List(1, 2, 3): @unchecked + val lst @ b :: bs = List(1, 2, 3): @unchecked + val (1, c) = (1, 2): @unchecked object Positive { def unapply(i: Int): Option[Int] = Some(i).filter(_ > 0) } - val Positive(p): @unchecked = 5 + val Positive(p) = 5: @unchecked } \ No newline at end of file From b3cff8be8e094fc5870ee7757d3f4ba63ff0df4b Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Mon, 14 Dec 2020 23:37:25 +0100 Subject: [PATCH 2/7] Fix unchecked PatDef in compiler --- .../dotty/tools/dotc/transform/ContextFunctionResults.scala | 6 +++--- compiler/src/dotty/tools/dotc/transform/Erasure.scala | 4 ++-- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ContextFunctionResults.scala b/compiler/src/dotty/tools/dotc/transform/ContextFunctionResults.scala index e21a835e2833..ee56767054fd 100644 --- a/compiler/src/dotty/tools/dotc/transform/ContextFunctionResults.scala +++ b/compiler/src/dotty/tools/dotc/transform/ContextFunctionResults.scala @@ -46,7 +46,7 @@ object ContextFunctionResults: def contextResultCount(sym: Symbol)(using Context): Int = sym.getAnnotation(defn.ContextResultCountAnnot) match case Some(annot) => - val ast.Trees.Literal(Constant(crCount: Int)) :: Nil: @unchecked = annot.arguments + val ast.Trees.Literal(Constant(crCount: Int)) :: Nil = annot.arguments: @unchecked crCount case none => 0 @@ -73,7 +73,7 @@ object ContextFunctionResults: def contextParamCount(tp: Type, crCount: Int): Int = if crCount == 0 then 0 else - val defn.ContextFunctionType(params, resTpe, isErased): @unchecked = tp + val defn.ContextFunctionType(params, resTpe, isErased) = tp: @unchecked val rest = contextParamCount(resTpe, crCount - 1) if isErased then rest else params.length + rest @@ -106,7 +106,7 @@ object ContextFunctionResults: def missingCR(tp: Type, crCount: Int): (Type, Int) = if crCount == 0 then (tp, 0) else - val defn.ContextFunctionType(formals, resTpe, isErased): @unchecked = tp + val defn.ContextFunctionType(formals, resTpe, isErased) = tp: @unchecked val result @ (rt, nparams) = missingCR(resTpe, crCount - 1) assert(nparams <= paramCount) if nparams == paramCount || isErased then result diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 7bb57c0ec5b1..54480c0be824 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -490,7 +490,7 @@ object Erasure { def abstracted(args: List[Tree], tp: Type, pt: Type)(using Context): Tree = if args.length < targetLength then try - val defn.ContextFunctionType(argTpes, resTpe, isErased): @unchecked = tp + val defn.ContextFunctionType(argTpes, resTpe, isErased) = tp: @unchecked if isErased then abstracted(args, resTpe, pt) else val anonFun = newSymbol( @@ -498,7 +498,7 @@ object Erasure { MethodType(argTpes, resTpe), coord = tree.span.endPos) anonFun.info = transformInfo(anonFun, anonFun.info) def lambdaBody(refss: List[List[Tree]]) = - val refs :: Nil : @unchecked = refss + val refs :: Nil = refss: @unchecked val expandedRefs = refs.map(_.withSpan(tree.span.endPos)) match case (bunchedParam @ Ident(nme.ALLARGS)) :: Nil => argTpes.indices.toList.map(n => diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 01d1ecdbf62b..8488d4c8ba36 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1882,7 +1882,7 @@ class Typer extends Namer case _ => var name = tree.name if (name == nme.WILDCARD && tree.mods.is(Given)) { - val Typed(_, tpt): @unchecked = tree.body + val Typed(_, tpt) = tree.body: @unchecked name = desugar.inventGivenOrExtensionName(tpt) } if (name == nme.WILDCARD) body1 From 70e390ef2c05c328ae3bd5e66b2bc73434f947b2 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 15 Dec 2020 00:55:23 +0100 Subject: [PATCH 3/7] Fix tests --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/run/unchecked-patterns.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 8488d4c8ba36..99cb703ba49d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1396,7 +1396,7 @@ class Typer extends Namer // skip exhaustivity check in later phase // TODO: move the check above to patternMatcher phase - val uncheckedTpe = AnnotatedType(sel.tpe, Annotation(defn.UncheckedAnnot)) + val uncheckedTpe = AnnotatedType(sel.tpe.widen, Annotation(defn.UncheckedAnnot)) tpd.cpy.Match(result)( selector = tpd.Typed(sel, tpd.TypeTree(uncheckedTpe)), cases = result.cases diff --git a/tests/run/unchecked-patterns.scala b/tests/run/unchecked-patterns.scala index 3e614fcbf457..219462dfde64 100644 --- a/tests/run/unchecked-patterns.scala +++ b/tests/run/unchecked-patterns.scala @@ -1,6 +1,6 @@ object Test extends App { val x: Int = 2: @unchecked - val (y1: Some[Int]) = Some(1): Option[Int]: @unchecked + val (y1: Some[Int]) = Some(1): Option[Int] @unchecked val a :: as = List(1, 2, 3): @unchecked val lst @ b :: bs = List(1, 2, 3): @unchecked From 276cf2729f0170160672fefd6ed2eafcf8d145d1 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 15 Dec 2020 01:34:28 +0100 Subject: [PATCH 4/7] Better error recovery The test case is tests/neg/deferred-patdef.scala --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 10 +++++++--- compiler/src/dotty/tools/dotc/typer/Checking.scala | 2 +- tests/neg-custom-args/infix.scala | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index a76a2e1d6007..4c457feb5650 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3225,9 +3225,13 @@ object Parsers { case IdPattern(id, t) => t.isEmpty case _ => false } - if rhs.isEmpty && !isAllIds then - syntaxError(ExpectedTokenButFound(EQUALS, in.token), Span(in.lastOffset)) - PatDef(mods, lhs, tpt, rhs) + val rhs2 = + if rhs.isEmpty && !isAllIds then + syntaxError(ExpectedTokenButFound(EQUALS, in.token), Span(in.lastOffset)) + errorTermTree + else + rhs + PatDef(mods, lhs, tpt, rhs2) } } diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 0d97ebd30907..9bb44dd16b84 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -696,7 +696,7 @@ trait Checking { def recur(pat: Tree, pt: Type): Boolean = !sourceVersion.isAtLeast(`3.1`) || // only for 3.1 for now since mitigations work only after this PR - pt.widen.hasAnnotation(defn.UncheckedAnnot) || { + pt.hasAnnotation(defn.UncheckedAnnot) || { patmatch.println(i"check irrefutable $pat: ${pat.tpe} against $pt") pat match { case Bind(_, pat1) => diff --git a/tests/neg-custom-args/infix.scala b/tests/neg-custom-args/infix.scala index dc015215bdde..f6f3053087dd 100644 --- a/tests/neg-custom-args/infix.scala +++ b/tests/neg-custom-args/infix.scala @@ -58,7 +58,7 @@ def test() = { val Pair(_, _) = p val _ Pair _ = p // error val _ `Pair` _ = p // OK - val (_ PP _): @unchecked = p // OK + val (_ PP _) = p: @unchecked // OK val q = Q(1, 2) val Q(_, _) = q From d9d3bea271df9bf3ffd8f266efc063720c757748 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 15 Dec 2020 12:02:31 +0100 Subject: [PATCH 5/7] Address review --- .../src/dotty/tools/dotc/ast/Desugar.scala | 21 +++++++++++-------- .../src/dotty/tools/dotc/typer/Checking.scala | 2 +- .../changed-features/pattern-bindings.md | 2 +- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 6c1f9fb2895f..65085b39e7bc 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1009,15 +1009,18 @@ object desugar { * IrrefutableGenFrom: sel with attachment `CheckIrrefutable -> checkMode` */ def makeSelector(sel: Tree, checkMode: MatchCheck)(using Context): Tree = - if (checkMode == MatchCheck.Exhaustive) sel - else { - if (checkMode == MatchCheck.None) - Annotated(sel, New(ref(defn.UncheckedAnnot.typeRef))) - else - // TODO: use `pushAttachment` and investigate duplicate attachment - sel.withAttachment(CheckIrrefutable, checkMode) - sel - } + checkMode match + case MatchCheck.None => + Annotated(sel, New(ref(defn.UncheckedAnnot.typeRef))) + + case MatchCheck.Exhaustive => + sel + + case MatchCheck.IrrefutablePatDef | MatchCheck.IrrefutableGenFrom => + // TODO: use `pushAttachment` and investigate duplicate attachment + sel.withAttachment(CheckIrrefutable, checkMode) + sel + end match /** If `pat` is a variable pattern, * diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 9bb44dd16b84..3c1a4dba40d4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -671,7 +671,7 @@ trait Checking { report.error(ex"$cls cannot be instantiated since it${rstatus.msg}", pos) } - /** Check that pattern `pat` is irrefutable for scrutinee tye `sel.tpe`. + /** Check that pattern `pat` is irrefutable for scrutinee type `sel.tpe`. * This means `sel` is either marked @unchecked or `sel.tpe` conforms to the * pattern's type. If pattern is an UnApply, do the check recursively. */ diff --git a/docs/docs/reference/changed-features/pattern-bindings.md b/docs/docs/reference/changed-features/pattern-bindings.md index f9cd850f17c1..1bd9d7a2a0d9 100644 --- a/docs/docs/reference/changed-features/pattern-bindings.md +++ b/docs/docs/reference/changed-features/pattern-bindings.md @@ -25,7 +25,7 @@ want to decompose it like this: ```scala val first :: rest = elems // error ``` -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: +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 right-hand side with an `@unchecked` annotation: ```scala val first :: rest = elems: @unchecked // OK ``` From 53a3d4a0180fce334333b94759d8d9f2f5750c63 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 15 Dec 2020 14:18:40 +0100 Subject: [PATCH 6/7] Issue warnings instead of errors 1. Warnings are more consistent with exhaustivity check where only warnings are issued 2. Errors are not friendly for prototyping, where the programmer makes assumptions about what values are possible. --- compiler/src/dotty/tools/dotc/typer/Checking.scala | 2 +- compiler/test/dotty/tools/dotc/CompilationTests.scala | 2 +- docs/docs/reference/changed-features/pattern-bindings.md | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 3c1a4dba40d4..bdc8e0c77970 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -684,7 +684,7 @@ trait Checking { val problem = if (pat.tpe <:< reportedPt) "is more specialized than" else "does not match" val fix = if (isPatDef) "adding `: @unchecked` after the expression" else "writing `case ` before the full pattern" val pos = if (isPatDef) sel.srcPos else pat.srcPos - report.errorOrMigrationWarning( + report.warning( ex"""pattern's type ${pat.tpe} $problem the right hand side expression's type $reportedPt | |If the narrowing is intentional, this can be communicated by $fix.${err.rewriteNotice}""", diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 0016bf987352..742469bdcf22 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -118,7 +118,7 @@ class CompilationTests { aggregateTests( compileFilesInDir("tests/neg", defaultOptions), compileFilesInDir("tests/neg-tailcall", defaultOptions), - compileFilesInDir("tests/neg-strict", defaultOptions.and("-source", "3.1")), + compileFilesInDir("tests/neg-strict", defaultOptions.and("-source", "3.1", "-Xfatal-warnings")), compileFilesInDir("tests/neg-no-kind-polymorphism", defaultOptions and "-Yno-kind-polymorphism"), compileFilesInDir("tests/neg-custom-args/deprecation", defaultOptions.and("-Xfatal-warnings", "-deprecation")), compileFilesInDir("tests/neg-custom-args/fatal-warnings", defaultOptions.and("-Xfatal-warnings")), diff --git a/docs/docs/reference/changed-features/pattern-bindings.md b/docs/docs/reference/changed-features/pattern-bindings.md index 1bd9d7a2a0d9..247bac649a99 100644 --- a/docs/docs/reference/changed-features/pattern-bindings.md +++ b/docs/docs/reference/changed-features/pattern-bindings.md @@ -6,7 +6,7 @@ title: "Pattern Bindings" In Scala 2, pattern bindings in `val` definitions and `for` expressions are loosely typed. Potentially failing matches are still accepted at compile-time, but may influence the program's runtime behavior. -From Scala 3.1 on, type checking rules will be tightened so that errors are reported at compile-time instead. +From Scala 3.1 on, type checking rules will be tightened so that warnings are reported at compile-time instead. ## Bindings in Pattern Definitions @@ -15,7 +15,7 @@ val xs: List[Any] = List(1, 2, 3) val (x: String) :: _ = xs // error: pattern's type String is more specialized // than the right hand side expression's type Any ``` -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: +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: ```scala val pair = (1, true) val (x, y) = pair @@ -25,7 +25,7 @@ want to decompose it like this: ```scala val first :: rest = elems // error ``` -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 right-hand side with an `@unchecked` annotation: +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: ```scala val first :: rest = elems: @unchecked // OK ``` @@ -40,7 +40,7 @@ val elems: List[Any] = List((1, 2), "hello", (3, 4)) for ((x, y) <- elems) yield (y, x) // error: pattern's type (Any, Any) is more specialized // than the right hand side expression's type Any ``` -This code gives a compile-time error in Scala 3.1 whereas in Scala 2 the list `elems` +This code gives a compile-time warning in Scala 3.1 whereas in Scala 2 the list `elems` is filtered to retain only the elements of tuple type that match the pattern `(x, y)`. The filtering functionality can be obtained in Scala 3 by prefixing the pattern with `case`: ```scala From 95d394abeada970ad0689c07cd4f1d43b28d34e6 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 15 Dec 2020 18:19:16 +0100 Subject: [PATCH 7/7] Fix CI --- tests/neg-strict/filtering-fors.scala | 4 +- tests/neg-strict/i1050.scala | 90 +++++++++++++-------------- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/tests/neg-strict/filtering-fors.scala b/tests/neg-strict/filtering-fors.scala index 784e354c53ff..df0224a5cea3 100644 --- a/tests/neg-strict/filtering-fors.scala +++ b/tests/neg-strict/filtering-fors.scala @@ -1,6 +1,6 @@ object Test { - val xs: List[Any] = ??? + val xs: List[AnyRef] = ??? for (x <- xs) do () // OK for (x: Any <- xs) do () // OK @@ -16,7 +16,7 @@ object Test { for (case (x: String) <- xs; (y, z) <- xs) do () // error for ((x: String) <- xs; case (y, z) <- xs) do () // error - val pairs: List[Any] = List((1, 2), "hello", (3, 4)) + val pairs: List[AnyRef] = List((1, 2), "hello", (3, 4)) for ((x, y) <- pairs) yield (y, x) // error for (case x: String <- xs) do () // OK diff --git a/tests/neg-strict/i1050.scala b/tests/neg-strict/i1050.scala index 1ade1366a9b8..fa3a84625f2a 100644 --- a/tests/neg-strict/i1050.scala +++ b/tests/neg-strict/i1050.scala @@ -95,35 +95,35 @@ object Tiark6 { } object Indirect { - trait B { type L >: Any } - trait A { type L <: Nothing } - trait U { - trait X { - val q: A & B = ??? - type M = q.L - } - final lazy val p: X = ??? - def brand(x: Any): p.M = x // error: conflicting bounds + trait B { type L >: Any } + trait A { type L <: Nothing } + trait U { + trait X { + val q: A & B = ??? + type M = q.L } + final lazy val p: X = ??? + def brand(x: Any): p.M = x // error: conflicting bounds + } def main(args: Array[String]): Unit = { val v = new U {} v.brand("boom!"): Nothing } } object Indirect2 { - trait B { type L >: Any } - trait A { type L <: Nothing } - trait U { - trait Y { - val r: A & B = ??? - } - trait X { - val q: Y = ??? - type M = q.r.L - } - final lazy val p: X = ??? - def brand(x: Any): p.M = x // error: conflicting bounds + trait B { type L >: Any } + trait A { type L <: Nothing } + trait U { + trait Y { + val r: A & B = ??? + } + trait X { + val q: Y = ??? + type M = q.r.L } + final lazy val p: X = ??? + def brand(x: Any): p.M = x // error: conflicting bounds + } def main(args: Array[String]): Unit = { val v = new U {} v.brand("boom!"): Nothing @@ -144,38 +144,38 @@ object Rec1 { } } object Rec2 { - trait B { type L >: Any } - trait A { type L <: Nothing } - trait U { - trait Y { - val r: A & B & Y - } - trait X { - val q: Y = ??? - type M = q.r.L - } - final lazy val p: X = ??? - def brand(x: Any): p.M = x // error: conflicting bounds + trait B { type L >: Any } + trait A { type L <: Nothing } + trait U { + trait Y { + val r: A & B & Y + } + trait X { + val q: Y = ??? + type M = q.r.L } + final lazy val p: X = ??? + def brand(x: Any): p.M = x // error: conflicting bounds + } def main(args: Array[String]): Unit = { val v = new U {} v.brand("boom!"): Nothing } } object Indirect3 { - trait B { type L >: Any } - trait A { type L <: Nothing } - trait U { - trait Y { - val r: Y & A & B = ??? - } - trait X { - val q: Y = ??? - type M = q.r.L - } - final lazy val p: X = ??? - def brand(x: Any): p.M = x // error: conflicting bounds + trait B { type L >: Any } + trait A { type L <: Nothing } + trait U { + trait Y { + val r: Y & A & B = ??? } + trait X { + val q: Y = ??? + type M = q.r.L + } + final lazy val p: X = ??? + def brand(x: Any): p.M = x // error: conflicting bounds + } def main(args: Array[String]): Unit = { val v = new U {} v.brand("boom!"): Nothing