From ab714c8c0f154e772986867a4248b7c4fca276ba Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 7 Jan 2021 12:31:39 +0100 Subject: [PATCH 1/5] Fix #10994: align typed pattern syntax to Scala 2 In Scala 2, a typed pattern `p: T` restricts that `p` can only be a pattern variable. In Dotty, #6919 allows `p` to be any pattern, in order to support pattern matching on generic number literals. This PR aligns the syntax with Scala 2 by stipulating that in a typed pattern `p: T`, either - `p` is a pattern variable, or - `p` is a number literal --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 6 ++++-- docs/docs/internals/syntax.md | 4 +++- tests/neg/i10994.scala | 2 ++ 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 tests/neg/i10994.scala diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 6577c6a12ff3..ecedf9c45249 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2601,11 +2601,13 @@ object Parsers { if (isIdent(nme.raw.BAR)) { in.nextToken(); pattern1(location) :: patternAlts(location) } else Nil - /** Pattern1 ::= Pattern2 [Ascription] + /** Pattern1 ::= PatVar Ascription + * | SimpleLiteral Ascription + * | Pattern2 */ def pattern1(location: Location = Location.InPattern): Tree = val p = pattern2() - if in.token == COLON then + if (isVarPattern(p) || p.isInstanceOf[Number]) && in.token == COLON then in.nextToken() ascription(p, location) else p diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 56648a81d4c2..40506f427361 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -263,7 +263,9 @@ TypeCaseClauses ::= TypeCaseClause { TypeCaseClause } TypeCaseClause ::= ‘case’ InfixType ‘=>’ Type [nl] Pattern ::= Pattern1 { ‘|’ Pattern1 } Alternative(pats) -Pattern1 ::= Pattern2 [‘:’ RefinedType] Bind(name, Typed(Ident(wildcard), tpe)) +Pattern1 ::= PatVar ‘:’ RefinedType Bind(name, Typed(Ident(wildcard), tpe)) + | SimpleLiteral ‘:’ RefinedType Bind(name, Typed(Ident(wildcard), tpe)) + | Pattern2 Pattern2 ::= [id ‘@’] InfixPattern Bind(name, pat) InfixPattern ::= SimplePattern { id [nl] SimplePattern } InfixOp(pat, op, pat) SimplePattern ::= PatVar Ident(wildcard) diff --git a/tests/neg/i10994.scala b/tests/neg/i10994.scala new file mode 100644 index 000000000000..ce5cb2cf3df9 --- /dev/null +++ b/tests/neg/i10994.scala @@ -0,0 +1,2 @@ +def foo = true match + case (b: Boolean): Boolean => () // error From 99e7c4e5f71aa5585a67acb0a2c0f00a459f1c60 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 7 Jan 2021 12:55:59 +0100 Subject: [PATCH 2/5] Fix bootstrapping --- compiler/src/dotty/tools/dotc/core/Types.scala | 4 ++-- compiler/src/dotty/tools/dotc/typer/Typer.scala | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 9273bef4b9f5..51aa4ef04d1e 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1223,7 +1223,7 @@ object Types { * then the top-level union isn't widened. This is needed so that type inference can infer nullable types. */ def widenUnion(using Context): Type = widen match - case tp @ OrNull(tp1): OrType => + case tp @ OrNull(tp1) => // Don't widen `T|Null`, since otherwise we wouldn't be able to infer nullable unions. val tp1Widen = tp1.widenUnionWithoutNull if (tp1Widen.isRef(defn.AnyClass)) tp1Widen @@ -3158,7 +3158,7 @@ object Types { object OrNull { def apply(tp: Type)(using Context) = OrType(tp, defn.NullType, soft = false) - def unapply(tp: Type)(using Context): Option[Type] = + def unapply(tp: OrType)(using Context): Option[Type] = if (ctx.explicitNulls) { val tp1 = tp.stripNull() if tp1 ne tp then Some(tp1) else None diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 7ab102186a5e..77cbe93f06b1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -425,13 +425,17 @@ class Typer extends Namer * (x: T | Null) => x.$asInstanceOf$[x.type & T] */ def toNotNullTermRef(tree: Tree, pt: Type)(using Context): Tree = tree.tpe match - case ref @ OrNull(tpnn) : TermRef + case ref: TermRef if pt != AssignProto && // Ensure it is not the lhs of Assign ctx.notNullInfos.impliesNotNull(ref) && // If a reference is in the context, it is already trackable at the point we add it. // Hence, we don't use isTracked in the next line, because checking use out of order is enough. !ref.usedOutOfOrder => - tree.select(defn.Any_typeCast).appliedToType(AndType(ref, tpnn)) + ref.widenDealias match + case OrNull(tpnn) => + tree.select(defn.Any_typeCast).appliedToType(AndType(ref, tpnn)) + case _ => + tree case _ => tree From 0b0e7b59f3489c97280fac8380c34e297c71c062 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 7 Jan 2021 16:20:47 +0100 Subject: [PATCH 3/5] Fix tests --- docs/docs/contributing/testing.md | 2 +- tests/neg/i8407.scala | 2 +- tests/pos/patmat.scala | 7 +------ tests/semanticdb/expect/ValPattern.expect.scala | 2 +- tests/semanticdb/expect/ValPattern.scala | 2 +- tests/semanticdb/metac.expect | 6 ++---- 6 files changed, 7 insertions(+), 14 deletions(-) diff --git a/docs/docs/contributing/testing.md b/docs/docs/contributing/testing.md index c0fdca5f10dc..1a8fa5eb56d8 100644 --- a/docs/docs/contributing/testing.md +++ b/docs/docs/contributing/testing.md @@ -157,5 +157,5 @@ Expect files are used as regression tests to detect changes in the compiler. The test suite will create a new file if it detects any difference, which can be compared with the original expect file, or if the user wants to globally replace all expect files for semanticdb they can use -`dotty-compiler-bootstrapped/test:runMain dotty.tools.dotc.semanticdb.updateExpect`, and compare the changes via version +`scala3-compiler-bootstrapped/test:runMain dotty.tools.dotc.semanticdb.updateExpect`, and compare the changes via version control. diff --git a/tests/neg/i8407.scala b/tests/neg/i8407.scala index 34d7fa20914c..de8e3c98a028 100644 --- a/tests/neg/i8407.scala +++ b/tests/neg/i8407.scala @@ -1,6 +1,6 @@ object Test: val xs = List(1, 2, 3, 4, 5) xs match { - case List(1, 2, xs1 @ xs2: _*) => println(xs2) // error // error + case List(1, 2, xs1 @ xs2: _*) => println(xs2) // error case _ => () } \ No newline at end of file diff --git a/tests/pos/patmat.scala b/tests/pos/patmat.scala index f34c5d98336d..46eb83f9142d 100644 --- a/tests/pos/patmat.scala +++ b/tests/pos/patmat.scala @@ -16,7 +16,7 @@ object Test { } (xs.length, xs) match { - case (0, Nil: List[Int]) => println("1") + case (0, Nil) => println("1") case (_, Nil) => println("2") case (0, _) => println("3") case (x, y) => println("4") @@ -46,9 +46,4 @@ object Test { case Some(s) => println(s) case None => println("nothing") } - - type IntPair = (Int, Int) - ??? match { - case (x, y): IntPair => x * y - } } diff --git a/tests/semanticdb/expect/ValPattern.expect.scala b/tests/semanticdb/expect/ValPattern.expect.scala index f89133f6ef5b..4369cc98fb65 100644 --- a/tests/semanticdb/expect/ValPattern.expect.scala +++ b/tests/semanticdb/expect/ValPattern.expect.scala @@ -6,7 +6,7 @@ class ValPattern/*<-example::ValPattern#*/ { val Some/*->scala::Some.*//*->scala::Some.unapply().*/(number1/*<-example::ValPattern#number1.*/) = Some/*->scala::Some.*//*->scala::Some.apply().*/(1) - val List/*->scala::package.List.*//*->scala::collection::SeqFactory#unapplySeq().*/(Some/*->scala::Some.*//*->scala::Some.unapply().*/(q1/*<-example::ValPattern#q1.*/), None/*->scala::None.*/: None/*->scala::None.*/.type, None/*->scala::None.*/) = ???/*->scala::Predef.`???`().*/ + val List/*->scala::package.List.*//*->scala::collection::SeqFactory#unapplySeq().*/(Some/*->scala::Some.*//*->scala::Some.unapply().*/(q1/*<-example::ValPattern#q1.*/), None/*->scala::None.*/) = ???/*->scala::Predef.`???`().*/ var (leftVar/*<-example::ValPattern#leftVar().*/, rightVar/*<-example::ValPattern#rightVar().*/) = (/*->scala::Tuple2.apply().*/1, 2) var Some/*->scala::Some.*//*->scala::Some.unapply().*/(number1Var/*<-example::ValPattern#number1Var().*/) = diff --git a/tests/semanticdb/expect/ValPattern.scala b/tests/semanticdb/expect/ValPattern.scala index b4e4ea3363c1..9d8923f0a45c 100644 --- a/tests/semanticdb/expect/ValPattern.scala +++ b/tests/semanticdb/expect/ValPattern.scala @@ -6,7 +6,7 @@ class ValPattern { val Some(number1) = Some(1) - val List(Some(q1), None: None.type, None) = ??? + val List(Some(q1), None) = ??? var (leftVar, rightVar) = (1, 2) var Some(number1Var) = diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 77bdc43c3182..7edb6d03aed8 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -2710,7 +2710,7 @@ Uri => ValPattern.scala Text => empty Language => Scala Symbols => 22 entries -Occurrences => 63 entries +Occurrences => 61 entries Symbols: example/ValPattern# => class ValPattern @@ -2754,9 +2754,7 @@ Occurrences: [8:15..8:15): -> scala/Some.unapply(). [8:16..8:18): q1 <- example/ValPattern#q1. [8:21..8:25): None -> scala/None. -[8:27..8:31): None -> scala/None. -[8:38..8:42): None -> scala/None. -[8:46..8:49): ??? -> scala/Predef.`???`(). +[8:29..8:32): ??? -> scala/Predef.`???`(). [10:7..10:14): leftVar <- example/ValPattern#leftVar(). [10:16..10:24): rightVar <- example/ValPattern#rightVar(). [10:29..10:29): -> scala/Tuple2.apply(). From 15d1aa0ee5c6cc486661c132813e65b6ec4b5221 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 7 Jan 2021 18:29:17 +0100 Subject: [PATCH 4/5] Fix NonNull test The test case `tests/explicit-nulls/neg/strip.scala` specify that null unions inside intersection types should work. After changing the scrutinee type of the extractor `OrNull` it is no longer the case. Changing the scrutinee type is still justified because it agrees with the name as well as the usage in `Types.scala`. In contrast, in `Typer.scala`, the logic is more clear without using `OrNull`. --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 77cbe93f06b1..3f43a697da48 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -426,15 +426,17 @@ class Typer extends Namer */ def toNotNullTermRef(tree: Tree, pt: Type)(using Context): Tree = tree.tpe match case ref: TermRef - if pt != AssignProto && // Ensure it is not the lhs of Assign + if ctx.explicitNulls && + pt != AssignProto && // Ensure it is not the lhs of Assign ctx.notNullInfos.impliesNotNull(ref) && // If a reference is in the context, it is already trackable at the point we add it. // Hence, we don't use isTracked in the next line, because checking use out of order is enough. !ref.usedOutOfOrder => - ref.widenDealias match - case OrNull(tpnn) => - tree.select(defn.Any_typeCast).appliedToType(AndType(ref, tpnn)) - case _ => + val tp1 = ref.widenDealias + val tp2 = tp1.stripNull() + if tp1 ne tp2 then + tree.select(defn.Any_typeCast).appliedToType(AndType(ref, tp2)) + else tree case _ => tree From 700c5f8c93bd482e4100b4fc3c5678e68190cff0 Mon Sep 17 00:00:00 2001 From: Fengyun Liu Date: Fri, 8 Jan 2021 15:28:29 +0100 Subject: [PATCH 5/5] Update docs/docs/internals/syntax.md Co-authored-by: Jamie Thompson --- docs/docs/internals/syntax.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 40506f427361..40b50a991347 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -264,7 +264,7 @@ TypeCaseClause ::= ‘case’ InfixType ‘=>’ Type [nl] Pattern ::= Pattern1 { ‘|’ Pattern1 } Alternative(pats) Pattern1 ::= PatVar ‘:’ RefinedType Bind(name, Typed(Ident(wildcard), tpe)) - | SimpleLiteral ‘:’ RefinedType Bind(name, Typed(Ident(wildcard), tpe)) + | SimpleLiteral ‘:’ RefinedType Typed(pat, tpe) | Pattern2 Pattern2 ::= [id ‘@’] InfixPattern Bind(name, pat) InfixPattern ::= SimplePattern { id [nl] SimplePattern } InfixOp(pat, op, pat)