From 2ea97618c301840c67d20af7267dc22550d40b1e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 26 Oct 2020 19:39:49 +0100 Subject: [PATCH 1/5] Fix #10087: Change syntax of given instances in patterns See the reference doc pages in this commit for details --- .../dotty/tools/dotc/parsing/Parsers.scala | 43 +++++++------------ docs/docs/internals/syntax.md | 4 +- docs/docs/reference/contextual/givens.md | 15 +++++++ docs/docs/reference/metaprogramming/inline.md | 24 +++++++---- tests/neg/given-pattern.scala | 4 +- tests/pos/given-pattern.scala | 14 +++--- tests/pos/i10087.scala | 13 ++++++ tests/pos/reference/delegate-match.scala | 4 +- 8 files changed, 72 insertions(+), 49 deletions(-) create mode 100644 tests/pos/i10087.scala diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index eea9977aad21..fc4c65fdab08 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2587,35 +2587,15 @@ object Parsers { else Nil /** Pattern1 ::= Pattern2 [Ascription] - * | ‘given’ PatVar ‘:’ RefinedType */ def pattern1(location: Location = Location.InPattern): Tree = - if (in.token == GIVEN) { - val givenMod = atSpan(in.skipToken())(Mod.Given()) - atSpan(in.offset) { - in.token match { - case IDENTIFIER | USCORE if in.name.isVarPattern => - val name = in.name - in.nextToken() - accept(COLON) - val typed = ascription(Ident(nme.WILDCARD), location) - Bind(name, typed).withMods(addMod(Modifiers(), givenMod)) - case _ => - syntaxErrorOrIncomplete("pattern variable expected") - errorTermTree - } - } - } - else { - val p = pattern2() - if (in.token == COLON) { - in.nextToken() - ascription(p, location) - } - else p - } + val p = pattern2() + if in.token == COLON then + in.nextToken() + ascription(p, location) + else p - /** Pattern2 ::= [id `@'] InfixPattern + /** Pattern2 ::= [id `as'] InfixPattern */ val pattern2: () => Tree = () => infixPattern() match { case p @ Ident(name) if in.token == AT || in.isIdent(nme.as) => @@ -2624,14 +2604,15 @@ object Parsers { val offset = in.skipToken() - // compatibility for Scala2 `x @ _*` syntax infixPattern() match { - case pt @ Ident(tpnme.WILDCARD_STAR) => + case pt @ Ident(tpnme.WILDCARD_STAR) => // compatibility for Scala2 `x @ _*` syntax if sourceVersion.isAtLeast(`3.1`) then report.errorOrMigrationWarning( "The syntax `x @ _*` is no longer supported; use `x : _*` instead", in.sourcePos(startOffset(p))) atSpan(startOffset(p), offset) { Typed(p, pt) } + case pt @ Bind(nme.WILDCARD, pt1: Typed) if pt.mods.is(Given) => + atSpan(startOffset(p), 0) { Bind(name, pt1).withMods(pt.mods) } case pt => atSpan(startOffset(p), 0) { Bind(name, pt) } } @@ -2685,6 +2666,12 @@ object Parsers { simpleExpr() case XMLSTART => xmlLiteralPattern() + case GIVEN => + atSpan(in.offset) { + val givenMod = atSpan(in.skipToken())(Mod.Given()) + val typed = Typed(Ident(nme.WILDCARD), refinedType()) + Bind(nme.WILDCARD, typed).withMods(addMod(Modifiers(), givenMod)) + } case _ => if (isLiteral) literal(inPattern = true) else { diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 9819bd4ee770..0da5fd9f6069 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -272,8 +272,7 @@ TypeCaseClause ::= ‘case’ InfixType ‘=>’ Type [nl] Pattern ::= Pattern1 { ‘|’ Pattern1 } Alternative(pats) Pattern1 ::= Pattern2 [‘:’ RefinedType] Bind(name, Typed(Ident(wildcard), tpe)) - | ‘given’ PatVar ‘:’ RefinedType -Pattern2 ::= [id ‘as’] InfixPattern Bind(name, pat) +Pattern2 ::= [id ‘as’] InfixPattern Bind(name, pat) InfixPattern ::= SimplePattern { id [nl] SimplePattern } InfixOp(pat, op, pat) SimplePattern ::= PatVar Ident(wildcard) | Literal Bind(name, Ident(wildcard)) @@ -281,6 +280,7 @@ SimplePattern ::= PatVar | Quoted | XmlPattern | SimplePattern1 [TypeArgs] [ArgumentPatterns] + | ‘given’ RefinedType SimplePattern1 ::= SimpleRef | SimplePattern1 ‘.’ id PatVar ::= varid diff --git a/docs/docs/reference/contextual/givens.md b/docs/docs/reference/contextual/givens.md index e4b72543545c..576eb584b3f3 100644 --- a/docs/docs/reference/contextual/givens.md +++ b/docs/docs/reference/contextual/givens.md @@ -90,6 +90,21 @@ transparent inline given mkAnnotations[A, T] as Annotations[A, T] = ${ ``` Since `mkAnnotations` is `transparent`, the type of an application is the type of its right hand side, which can be a proper subtype of the declared result type `Annotations[A, T]`. +## Pattern-Bound Given Instances + +Given instances can also appear as pattern bound-variables. Example: + +```scala +for given Context <- applicationContexts do + +pair match + case (ctx as given Context, y) => ... +``` +In the first fragment above, anonymous given instances for class `Context` are established by enumerating over `applicationContexts`. In the the second fragment, a given `Context` +instance named `ctx` is established by matching against the first half of the `pair` selector. + +In each case, a pattern-bound given instance consists of `given` and a type `T`. The pattern matches exactly the same selectors as the type ascription pattern `_: T`. + ## Given Instance Initialization A given instance without type or context parameters is initialized on-demand, the first diff --git a/docs/docs/reference/metaprogramming/inline.md b/docs/docs/reference/metaprogramming/inline.md index 998da914f615..ecbd2325611e 100644 --- a/docs/docs/reference/metaprogramming/inline.md +++ b/docs/docs/reference/metaprogramming/inline.md @@ -268,10 +268,10 @@ val obj2 = choose(false) // static type is B // obj1.m() // compile-time error: `m` is not defined on `A` obj2.m() // OK ``` -Here, the inline method `choose` returns an instance of either of the two types `A` or `B`. +Here, the inline method `choose` returns an instance of either of the two types `A` or `B`. If `choose` had not been declared to be `transparent`, the result -of its expansion would always be of type `A`, even though the computed value might be of the subtype `B`. -The inline method is a "blackbox" in the sense that details of its implementation do not leak out. +of its expansion would always be of type `A`, even though the computed value might be of the subtype `B`. +The inline method is a "blackbox" in the sense that details of its implementation do not leak out. But if a `transparent` modifier is given, the expansion is the type of the expanded body. If the argument `b` is `true`, that type is `A`, otherwise it is `B`. Consequently, calling `m` on `obj2` type-checks since `obj2` has the same type as the expansion of `choose(false)`, which is `B`. @@ -571,16 +571,24 @@ would use it as follows: import scala.compiletime.summonFrom inline def setFor[T]: Set[T] = summonFrom { - case given ord: Ordering[T] => new TreeSet[T] - case _ => new HashSet[T] + case ord: Ordering[T] => new TreeSet[T](using ord) + case _ => new HashSet[T] } ``` A `summonFrom` call takes a pattern matching closure as argument. All patterns in the closure are type ascriptions of the form `identifier : Type`. -Patterns are tried in sequence. The first case with a pattern `x: T` such that -an implicit value of type `T` can be summoned is chosen. If the pattern is prefixed -with `given`, the variable `x` is bound to the implicit value for the remainder of the case. It can in turn be used as an implicit in the right hand side of the case. It is an error if one of the tested patterns gives rise to an ambiguous implicit search. +Patterns are tried in sequence. The first case with a pattern `x: T` such that an implicit value of type `T` can be summoned is chosen. + +Alternatively, one can also use a pattern-bound given instance, which avoids the explicit using clause. For instance, `setFor` could also be formulated as follows: +```scala +import scala.compiletime.summonFrom + +inline def setFor[T]: Set[T] = summonFrom { + case given Ordering[T] => new TreeSet[T] + case _ => new HashSet[T] +} +``` `summonFrom` applications must be reduced at compile time. diff --git a/tests/neg/given-pattern.scala b/tests/neg/given-pattern.scala index 9902a548f633..72f5213c9f06 100644 --- a/tests/neg/given-pattern.scala +++ b/tests/neg/given-pattern.scala @@ -4,11 +4,11 @@ class Test { import scala.collection.immutable.{TreeSet, HashSet} def f2[T](x: Ordering[T]) = { - val (given y: Ordering[T]) = x + val (given Ordering[T]) = x new TreeSet[T] // error: no implicit ordering defined for T } def f3[T](x: Ordering[T]) = { - val given y: Ordering[T] = x // error: pattern expected + val given Ordering[T] = x new TreeSet[T] // error: no implicit ordering defined for T } } \ No newline at end of file diff --git a/tests/pos/given-pattern.scala b/tests/pos/given-pattern.scala index db655d093482..6267e23193e4 100644 --- a/tests/pos/given-pattern.scala +++ b/tests/pos/given-pattern.scala @@ -6,25 +6,25 @@ class Test { transparent inline def trySummon[S, T](f: PartialFunction[S, T]): T = ??? inline def setFor[T]: Set[T] = trySummon { - case given ord: Ordering[T] => new TreeSet[T] - case given _: Ordering[T] => new TreeSet[T] - case _ => new HashSet[T] + case ord as given Ordering[T] => new TreeSet[T] + case given Ordering[T] => new TreeSet[T] + case _ => new HashSet[T] } def f1[T](x: Ordering[T]) = (x, x) match { - case (given y: Ordering[T], _) => new TreeSet[T] + case (y as given Ordering[T], _) => new TreeSet[T] } def f2[T](x: Ordering[T]) = { val xs = List(x, x, x) - for given y: Ordering[T] <- xs + for y as given Ordering[T] <- xs yield new TreeSet[T] } def f3[T](x: Ordering[T]) = (x, x) match { - case (given _: Ordering[T], _) => new TreeSet[T] + case (given Ordering[T], _) => new TreeSet[T] } def f4[T](x: Ordering[T]) = { val xs = List(x, x, x) - for given _: Ordering[T] <- xs + for given Ordering[T] <- xs yield new TreeSet[T] } } \ No newline at end of file diff --git a/tests/pos/i10087.scala b/tests/pos/i10087.scala new file mode 100644 index 000000000000..7c9cd1ca24e3 --- /dev/null +++ b/tests/pos/i10087.scala @@ -0,0 +1,13 @@ +import math.Ordering +import collection.immutable.TreeSet + +def f1[T](x: Ordering[T]) = (x, x) match { + case (given Ordering[T], _) => new TreeSet[T] +} +def f4[T](x: Ordering[T]) = { + val xs = List(x, x, x) + for given Ordering[T] <- xs + yield new TreeSet[T] + for x as given Ordering[T] <- xs + yield new TreeSet[T] +} \ No newline at end of file diff --git a/tests/pos/reference/delegate-match.scala b/tests/pos/reference/delegate-match.scala index 2f1e1379ecc6..4e81c63af37b 100644 --- a/tests/pos/reference/delegate-match.scala +++ b/tests/pos/reference/delegate-match.scala @@ -5,8 +5,8 @@ class Test extends App: import scala.compiletime.summonFrom transparent inline def setFor[T]: Set[T] = summonFrom { - case given ord: Ordering[T] => new TreeSet[T] - case _ => new HashSet[T] + case ord as given Ordering[T] => new TreeSet[T] + case _ => new HashSet[T] } summon[Ordering[String]] From eeb02ca1fd0ba5eada18b259e19f5088d7d33189 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 26 Oct 2020 19:47:12 +0100 Subject: [PATCH 2/5] Remove duplication in infixPattern --- .../src/dotty/tools/dotc/parsing/Parsers.scala | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index fc4c65fdab08..89e1b79d333b 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2603,13 +2603,9 @@ object Parsers { deprecationWarning(s"`@` bindings have been deprecated; use `as` instead", in.offset) val offset = in.skipToken() - infixPattern() match { case pt @ Ident(tpnme.WILDCARD_STAR) => // compatibility for Scala2 `x @ _*` syntax - if sourceVersion.isAtLeast(`3.1`) then - report.errorOrMigrationWarning( - "The syntax `x @ _*` is no longer supported; use `x : _*` instead", - in.sourcePos(startOffset(p))) + warnMigration(p) atSpan(startOffset(p), offset) { Typed(p, pt) } case pt @ Bind(nme.WILDCARD, pt1: Typed) if pt.mods.is(Given) => atSpan(startOffset(p), 0) { Bind(name, pt1).withMods(pt.mods) } @@ -2617,16 +2613,18 @@ object Parsers { atSpan(startOffset(p), 0) { Bind(name, pt) } } case p @ Ident(tpnme.WILDCARD_STAR) => - // compatibility for Scala2 `_*` syntax - if sourceVersion.isAtLeast(`3.1`) then - report.errorOrMigrationWarning( - "The syntax `_*` is no longer supported; use `x : _*` instead", - in.sourcePos(startOffset(p))) + warnMigration(p) atSpan(startOffset(p)) { Typed(Ident(nme.WILDCARD), p) } case p => p } + private def warnMigration(p: Tree) = + if sourceVersion.isAtLeast(`3.1`) then + report.errorOrMigrationWarning( + "The syntax `x @ _*` is no longer supported; use `x : _*` instead", + in.sourcePos(startOffset(p))) + /** InfixPattern ::= SimplePattern {id [nl] SimplePattern} */ def infixPattern(): Tree = From 18cdb7cab5a687f74857f4faa77f09c1992999de Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 26 Oct 2020 21:45:21 +0100 Subject: [PATCH 3/5] Fix tests --- tests/pos-macros/i7358.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pos-macros/i7358.scala b/tests/pos-macros/i7358.scala index 8aa6cc5d280a..1603bd6fbf49 100644 --- a/tests/pos-macros/i7358.scala +++ b/tests/pos-macros/i7358.scala @@ -8,7 +8,7 @@ transparent inline def summonT[Tp <: Tuple](using QuoteContext): Tuple = inline case _ : (hd *: tl) => { type H = hd summonFrom { - case given _ : Type[H] => summon[Type[H]] *: summonT[tl] + case given Type[H] => summon[Type[H]] *: summonT[tl] } } } From 746cff75bf856a82eb8b33a59f981cf12cccdc49 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 26 Oct 2020 22:05:54 +0100 Subject: [PATCH 4/5] Fix shapeless --- community-build/community-projects/shapeless | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community-build/community-projects/shapeless b/community-build/community-projects/shapeless index 3c11fae956e5..69ffc1f316cb 160000 --- a/community-build/community-projects/shapeless +++ b/community-build/community-projects/shapeless @@ -1 +1 @@ -Subproject commit 3c11fae956e5f12e2a69e4cdbf3b278b38bb689f +Subproject commit 69ffc1f316cb3c2c5d85a346f04276dda930d242 From 718dacd21fe12a1d5c2b9afa22c5705413637395 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 27 Oct 2020 10:13:32 +0100 Subject: [PATCH 5/5] Update docs/docs/reference/contextual/givens.md --- docs/docs/reference/contextual/givens.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/contextual/givens.md b/docs/docs/reference/contextual/givens.md index 576eb584b3f3..98f2f4504c18 100644 --- a/docs/docs/reference/contextual/givens.md +++ b/docs/docs/reference/contextual/givens.md @@ -100,7 +100,7 @@ for given Context <- applicationContexts do pair match case (ctx as given Context, y) => ... ``` -In the first fragment above, anonymous given instances for class `Context` are established by enumerating over `applicationContexts`. In the the second fragment, a given `Context` +In the first fragment above, anonymous given instances for class `Context` are established by enumerating over `applicationContexts`. In the second fragment, a given `Context` instance named `ctx` is established by matching against the first half of the `pair` selector. In each case, a pattern-bound given instance consists of `given` and a type `T`. The pattern matches exactly the same selectors as the type ascription pattern `_: T`.