Skip to content

Commit b32ec8e

Browse files
committed
Allow given bindings in patterns
Syntax: given x: T Allowed anywhere a pattern is allowed. This is useful in for expressions and as a replacement of given matches, to name just two cases.
1 parent ae44f1b commit b32ec8e

File tree

9 files changed

+108
-32
lines changed

9 files changed

+108
-32
lines changed

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

+5-1
Original file line numberDiff line numberDiff line change
@@ -930,14 +930,18 @@ object desugar {
930930
else tree
931931
}
932932

933+
/** Invent a name for an anonympus given of type or template `impl`. */
934+
def inventGivenName(impl: Tree)(implicit ctx: Context): SimpleName =
935+
avoidIllegalChars(s"${inventName(impl)}_given".toTermName.asSimpleName)
936+
933937
/** The normalized name of `mdef`. This means
934938
* 1. Check that the name does not redefine a Scala core class.
935939
* If it does redefine, issue an error and return a mangled name instead of the original one.
936940
* 2. If the name is missing (this can be the case for instance definitions), invent one instead.
937941
*/
938942
def normalizeName(mdef: MemberDef, impl: Tree)(implicit ctx: Context): Name = {
939943
var name = mdef.name
940-
if (name.isEmpty) name = name.likeSpaced(avoidIllegalChars(s"${inventName(impl)}_given".toTermName.asSimpleName))
944+
if (name.isEmpty) name = name.likeSpaced(inventGivenName(impl))
941945
if (ctx.owner == defn.ScalaPackageClass && defn.reservedScalaClassNames.contains(name.toTypeName)) {
942946
def kind = if (name.isTypeName) "class" else "object"
943947
ctx.error(em"illegal redefinition of standard $kind $name", mdef.sourcePos)

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

+17-16
Original file line numberDiff line numberDiff line change
@@ -308,25 +308,12 @@ object Trees {
308308
/** Tree defines a new symbol */
309309
trait DefTree[-T >: Untyped] extends DenotingTree[T] {
310310
type ThisTree[-T >: Untyped] <: DefTree[T]
311-
override def isDef: Boolean = true
312-
def namedType: NamedType = tpe.asInstanceOf[NamedType]
313-
}
314-
315-
/** Tree defines a new symbol and carries modifiers.
316-
* The position of a MemberDef contains only the defined identifier or pattern.
317-
* The envelope of a MemberDef contains the whole definition and has its point
318-
* on the opening keyword (or the next token after that if keyword is missing).
319-
*/
320-
abstract class MemberDef[-T >: Untyped](implicit @constructorOnly src: SourceFile) extends NameTree[T] with DefTree[T] {
321-
type ThisTree[-T >: Untyped] <: MemberDef[T]
322311

323312
private[this] var myMods: untpd.Modifiers = null
324313

325314
private[dotc] def rawMods: untpd.Modifiers =
326315
if (myMods == null) untpd.EmptyModifiers else myMods
327316

328-
def rawComment: Option[Comment] = getAttachment(DocComment)
329-
330317
def withAnnotations(annots: List[untpd.Tree]): ThisTree[Untyped] = withMods(rawMods.withAnnotations(annots))
331318

332319
def withMods(mods: untpd.Modifiers): ThisTree[Untyped] = {
@@ -338,14 +325,28 @@ object Trees {
338325
def withFlags(flags: FlagSet): ThisTree[Untyped] = withMods(untpd.Modifiers(flags))
339326
def withAddedFlags(flags: FlagSet): ThisTree[Untyped] = withMods(rawMods | flags)
340327

328+
/** Destructively update modifiers. To be used with care. */
329+
def setMods(mods: untpd.Modifiers): Unit = myMods = mods
330+
331+
override def isDef: Boolean = true
332+
def namedType: NamedType = tpe.asInstanceOf[NamedType]
333+
}
334+
335+
/** Tree defines a new symbol and carries modifiers.
336+
* The position of a MemberDef contains only the defined identifier or pattern.
337+
* The envelope of a MemberDef contains the whole definition and has its point
338+
* on the opening keyword (or the next token after that if keyword is missing).
339+
*/
340+
abstract class MemberDef[-T >: Untyped](implicit @constructorOnly src: SourceFile) extends NameTree[T] with DefTree[T] {
341+
type ThisTree[-T >: Untyped] <: MemberDef[T]
342+
343+
def rawComment: Option[Comment] = getAttachment(DocComment)
344+
341345
def setComment(comment: Option[Comment]): this.type = {
342346
comment.map(putAttachment(DocComment, _))
343347
this
344348
}
345349

346-
/** Destructively update modifiers. To be used with care. */
347-
def setMods(mods: untpd.Modifiers): Unit = myMods = mods
348-
349350
/** The position of the name defined by this definition.
350351
* This is a point position if the definition is synthetic, or a range position
351352
* if the definition comes from source.

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
457457

458458
abstract class ModsDecorator { def mods: Modifiers }
459459

460-
implicit class modsDeco(val mdef: MemberDef)(implicit ctx: Context) {
460+
implicit class modsDeco(val mdef: DefTree)(implicit ctx: Context) {
461461
def mods: Modifiers = mdef.rawMods
462462
}
463463

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

+27-8
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import Flags._
1414
import Contexts._
1515
import Names._
1616
import NameKinds.WildcardParamName
17+
import NameOps._
1718
import ast.{Positioned, Trees}
1819
import ast.Trees._
1920
import StdNames._
@@ -2350,16 +2351,34 @@ object Parsers {
23502351
if (isIdent(nme.raw.BAR)) { in.nextToken(); pattern1() :: patternAlts() }
23512352
else Nil
23522353

2353-
/** Pattern1 ::= Pattern2 [Ascription]
2354+
/** Pattern1 ::= Pattern2 [Ascription]
2355+
* | ‘given’ PatVar ‘:’ RefinedType
23542356
*/
2355-
def pattern1(): Tree = {
2356-
val p = pattern2()
2357-
if (in.token == COLON) {
2358-
in.nextToken()
2359-
ascription(p, Location.InPattern)
2357+
def pattern1(): Tree =
2358+
if (in.token == GIVEN) {
2359+
val givenMod = atSpan(in.skipToken())(Mod.Given())
2360+
atSpan(in.offset) {
2361+
in.token match {
2362+
case IDENTIFIER | USCORE if in.name.isVariableName =>
2363+
val name = in.name
2364+
in.nextToken()
2365+
accept(COLON)
2366+
val typed = ascription(Ident(nme.WILDCARD), Location.InPattern)
2367+
Bind(name, typed).withMods(addMod(Modifiers(), givenMod))
2368+
case _ =>
2369+
syntaxErrorOrIncomplete("pattern variable expected")
2370+
errorTermTree
2371+
}
2372+
}
2373+
}
2374+
else {
2375+
val p = pattern2()
2376+
if (in.token == COLON) {
2377+
in.nextToken()
2378+
ascription(p, Location.InPattern)
2379+
}
2380+
else p
23602381
}
2361-
else p
2362-
}
23632382

23642383
/** Pattern2 ::= [id `@'] InfixPattern
23652384
*/

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
479479
if (lo eq hi) optText(lo)(" = " ~ _)
480480
else optText(lo)(" >: " ~ _) ~ optText(hi)(" <: " ~ _)
481481
case Bind(name, body) =>
482-
("given ": Text).provided(tree.symbol.is(Implicit) && !homogenizedView) ~ // Used for scala.quoted.Type in quote patterns (not pickled)
482+
("given ": Text).provided(tree.symbol.isOneOf(GivenOrImplicit) && !homogenizedView) ~ // Used for scala.quoted.Type in quote patterns (not pickled)
483483
changePrec(InfixPrec) { toText(name) ~ " @ " ~ toText(body) }
484484
case Alternative(trees) =>
485485
changePrec(OrPrec) { toText(trees, " | ") }
@@ -699,7 +699,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
699699
}
700700

701701
/** Print modifiers from symbols if tree has type, overriding the untpd behavior. */
702-
implicit def modsDeco(mdef: untpd.MemberDef)(implicit ctx: Context): untpd.ModsDecorator =
702+
implicit def modsDeco(mdef: untpd.DefTree)(implicit ctx: Context): untpd.ModsDecorator =
703703
new untpd.ModsDecorator {
704704
def mods = if (mdef.hasType) Modifiers(mdef.symbol) else mdef.rawMods
705705
}

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

+9-4
Original file line numberDiff line numberDiff line change
@@ -1478,18 +1478,23 @@ class Typer extends Namer
14781478
tpd.cpy.UnApply(body1)(fn, Nil,
14791479
typed(untpd.Bind(tree.name, untpd.TypedSplice(arg)).withSpan(tree.span), arg.tpe) :: Nil)
14801480
case _ =>
1481-
if (tree.name == nme.WILDCARD) body1
1481+
var name = tree.name
1482+
if (name == nme.WILDCARD && tree.mods.is(Given)) {
1483+
val Typed(_, tpt): @unchecked = tree.body
1484+
name = desugar.inventGivenName(tpt)
1485+
}
1486+
if (name == nme.WILDCARD) body1
14821487
else {
14831488
// for a singleton pattern like `x @ Nil`, `x` should get the type from the scrutinee
14841489
// see tests/neg/i3200b.scala and SI-1503
14851490
val symTp =
14861491
if (body1.tpe.isInstanceOf[TermRef]) pt1
14871492
else body1.tpe.underlyingIfRepeated(isJava = false)
1488-
val sym = ctx.newPatternBoundSymbol(tree.name, symTp, tree.span)
1489-
if (pt == defn.ImplicitScrutineeTypeRef) sym.setFlag(Given)
1493+
val sym = ctx.newPatternBoundSymbol(name, symTp, tree.span)
1494+
if (pt == defn.ImplicitScrutineeTypeRef || tree.mods.is(Given)) sym.setFlag(Given)
14901495
if (ctx.mode.is(Mode.InPatternAlternative))
14911496
ctx.error(i"Illegal variable ${sym.name} in pattern alternative", tree.sourcePos)
1492-
assignType(cpy.Bind(tree)(tree.name, body1), sym)
1497+
assignType(cpy.Bind(tree)(name, body1), sym)
14931498
}
14941499
}
14951500
}

docs/docs/internals/syntax.md

+2
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ TypeCaseClause ::= ‘case’ InfixType ‘=>’ Type [nl]
262262
263263
Pattern ::= Pattern1 { ‘|’ Pattern1 } Alternative(pats)
264264
Pattern1 ::= Pattern2 [‘:’ RefinedType] Bind(name, Typed(Ident(wildcard), tpe))
265+
| ‘given’ PatVar ‘:’ RefinedType
265266
Pattern2 ::= [id ‘@’] InfixPattern Bind(name, pat)
266267
InfixPattern ::= SimplePattern { id [cnl] SimplePattern } InfixOp(pat, op, pat)
267268
SimplePattern ::= PatVar Ident(wildcard)
@@ -388,6 +389,7 @@ ObjectDef ::= id [Template]
388389
EnumDef ::= id ClassConstr InheritClauses EnumBody EnumDef(mods, name, tparams, template)
389390
GivenDef ::= [GivenSig (‘:’ | <:)] Type ‘=’ Expr
390391
| [GivenSig ‘:’] [ConstrApp {‘,’ ConstrApp }] [TemplateBody]
392+
| [GivenSig ‘:’] [DefTypeParamClause] DefParamClause TemplateBody
391393
GivenSig ::= [id] [DefTypeParamClause] {GivenParamClause}
392394
Template ::= InheritClauses [TemplateBody] Template(constr, parents, self, stats)
393395
InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}]

tests/neg/given-pattern.scala

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
3+
class Test {
4+
import scala.collection.immutable.{TreeSet, HashSet}
5+
6+
def f2[T](x: Ordering[T]) = {
7+
val (given y: Ordering[T]) = x
8+
new TreeSet[T] // error: no implicit ordering defined for T
9+
}
10+
def f3[T](x: Ordering[T]) = {
11+
val given y: Ordering[T] = x // error: pattern expected
12+
new TreeSet[T] // error: no implicit ordering defined for T
13+
}
14+
}

tests/pos/given-pattern.scala

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
3+
class Test {
4+
import scala.collection.immutable.{TreeSet, HashSet}
5+
import compiletime.trySummon
6+
7+
inline def trySummon[S, T](f: PartialFunction[S, T]) <: T = ???
8+
9+
inline def setFor[T]: Set[T] = trySummon {
10+
case given ord: Ordering[T] => new TreeSet[T]
11+
case given _: Ordering[T] => new TreeSet[T]
12+
case _ => new HashSet[T]
13+
}
14+
15+
def f1[T](x: Ordering[T]) = (x, x) match {
16+
case (given y: Ordering[T], _) => new TreeSet[T]
17+
}
18+
def f2[T](x: Ordering[T]) = {
19+
val xs = List(x, x, x)
20+
for given y: Ordering[T] <- xs
21+
yield new TreeSet[T]
22+
}
23+
def f3[T](x: Ordering[T]) = (x, x) match {
24+
case (given _: Ordering[T], _) => new TreeSet[T]
25+
}
26+
def f4[T](x: Ordering[T]) = {
27+
val xs = List(x, x, x)
28+
for given _: Ordering[T] <- xs
29+
yield new TreeSet[T]
30+
}
31+
}

0 commit comments

Comments
 (0)