Skip to content

Fix #10087 : Change syntax of given instances in patterns #10091

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 30, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 23 additions & 38 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2587,65 +2587,44 @@ 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) =>
if in.token == AT && sourceVersion.isAtLeast(`3.1`) then
deprecationWarning(s"`@` bindings have been deprecated; use `as` instead", in.offset)

val offset = in.skipToken()

// compatibility for Scala2 `x @ _*` syntax
infixPattern() match {
case pt @ Ident(tpnme.WILDCARD_STAR) =>
if sourceVersion.isAtLeast(`3.1`) then
report.errorOrMigrationWarning(
"The syntax `x @ _*` is no longer supported; use `x : _*` instead",
in.sourcePos(startOffset(p)))
case pt @ Ident(tpnme.WILDCARD_STAR) => // compatibility for Scala2 `x @ _*` syntax
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) }
case pt =>
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 =
Expand Down Expand Up @@ -2685,6 +2664,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 {
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/internals/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,15 +272,15 @@ 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))
| ‘(’ [Patterns] ‘)’ Parens(pats) Tuple(pats)
| Quoted
| XmlPattern
| SimplePattern1 [TypeArgs] [ArgumentPatterns]
| ‘given’ RefinedType
SimplePattern1 ::= SimpleRef
| SimplePattern1 ‘.’ id
PatVar ::= varid
Expand Down
15 changes: 15 additions & 0 deletions docs/docs/reference/contextual/givens.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) => ...
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would we re-write the following pattern to make x, y and z givens?

case x @ (y: Y, z @ Z()) =>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have tests with nested given patterns.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can't make x a given here. for x and y it would simply be:

case (x as given Y, y as given Z)

We already have a test like this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be a core limitation of the syntax.

The given statement syntax is

given X
given x as X

What if we used the same in patterns?

case given X =>
case given x as X =>

Then we could extend it to

case given x @ X(y, z) =>

```
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
Expand Down
24 changes: 16 additions & 8 deletions docs/docs/reference/metaprogramming/inline.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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.

Expand Down
4 changes: 2 additions & 2 deletions tests/neg/given-pattern.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. This allows for a given to be bound to a val which we could not do with given y as Ordering[T] = x

val (y as given Ordering[T]) = x

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand this correctly, the y is not implicit here (hence the error on next line), and the given Ordering is not visible outside of the pattern its part of, so while this syntax is permitted because any pattern can be used in a val, it's useless.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@smarter: correct. implicits in patterns behave the same way.

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this one work as a val definition in class?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, it should work

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then we should have some tests

new TreeSet[T] // error: no implicit ordering defined for T
}
}
2 changes: 1 addition & 1 deletion tests/pos-macros/i7358.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]
}
}
}
Expand Down
14 changes: 7 additions & 7 deletions tests/pos/given-pattern.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]
}
}
13 changes: 13 additions & 0 deletions tests/pos/i10087.scala
Original file line number Diff line number Diff line change
@@ -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]
}
4 changes: 2 additions & 2 deletions tests/pos/reference/delegate-match.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
Expand Down