Skip to content

Commit 9ac0ee0

Browse files
committed
Parsing of contextual (with) parameters and arguments
1 parent 6644875 commit 9ac0ee0

File tree

9 files changed

+235
-67
lines changed

9 files changed

+235
-67
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
8989
case class GenAlias(pat: Tree, expr: Tree) extends Tree
9090
case class ContextBounds(bounds: TypeBoundsTree, cxBounds: List[Tree]) extends TypTree
9191
case class PatDef(mods: Modifiers, pats: List[Tree], tpt: Tree, rhs: Tree) extends DefTree
92+
9293
case class DependentTypeTree(tp: List[Symbol] => Type) extends Tree
9394

9495
@sharable object EmptyTypeIdent extends Ident(tpnme.EMPTY) with WithoutTypeOrPos[Untyped] {
@@ -255,6 +256,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
255256
*/
256257
val OriginalSymbol: Property.Key[Symbol] = new Property.Key
257258

259+
/** Property key for contextual Apply trees of the form `fn with arg` */
260+
val WithApply: Property.StickyKey[Unit] = new Property.StickyKey
261+
258262
// ------ Creation methods for untyped only -----------------
259263

260264
def Ident(name: Name): Ident = new Ident(name)

compiler/src/dotty/tools/dotc/core/Flags.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,6 @@ object Flags {
312312
final val Contravariant: FlagSet = typeFlag(21, "<contravariant>")
313313
final val Label: FlagSet = termFlag(21, "<label>")
314314

315-
316315
/** A trait that has only abstract methods as members
317316
* and therefore can be represented by a Java interface.
318317
* Warning: flag is set during regular typer pass, should be tested only after typer.
@@ -348,6 +347,9 @@ object Flags {
348347
/** An extension method */
349348
final val Extension = termFlag(28, "<extension>")
350349

350+
/** A contextual (with) parameter */
351+
final val Contextual = commonFlag(29, "<contextual>")
352+
351353
/** Symbol is defined by a Java class */
352354
final val JavaDefined: FlagSet = commonFlag(30, "<java>")
353355

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

Lines changed: 92 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,8 @@ object Parsers {
502502
recur(top)
503503
}
504504

505-
/** operand { infixop operand} [postfixop],
505+
/** operand { infixop operand | ‘with’ (operand | ParArgumentExprs) } [postfixop],
506+
*
506507
* respecting rules of associativity and precedence.
507508
* @param notAnOperator a token that does not count as operator.
508509
* @param maybePostfix postfix operators are allowed.
@@ -513,23 +514,37 @@ object Parsers {
513514
isOperator: => Boolean = true,
514515
maybePostfix: Boolean = false): Tree = {
515516
val base = opStack
516-
var top = first
517-
while (isIdent && isOperator) {
518-
val op = if (isType) typeIdent() else termIdent()
519-
top = reduceStack(base, top, precedence(op.name), isLeftAssoc(op.name), op.name, isType)
520-
opStack = OpInfo(top, op, in.offset) :: opStack
521-
newLineOptWhenFollowing(canStartOperand)
522-
if (maybePostfix && !canStartOperand(in.token)) {
523-
val topInfo = opStack.head
524-
opStack = opStack.tail
525-
val od = reduceStack(base, topInfo.operand, 0, true, in.name, isType)
526-
return atPos(startOffset(od), topInfo.offset) {
527-
PostfixOp(od, topInfo.operator)
517+
518+
def recur(top: Tree): Tree =
519+
if (isIdent && isOperator) {
520+
val op = if (isType) typeIdent() else termIdent()
521+
val top1 = reduceStack(base, top, precedence(op.name), isLeftAssoc(op.name), op.name, isType)
522+
opStack = OpInfo(top1, op, in.offset) :: opStack
523+
newLineOptWhenFollowing(canStartOperand)
524+
if (maybePostfix && !canStartOperand(in.token)) {
525+
val topInfo = opStack.head
526+
opStack = opStack.tail
527+
val od = reduceStack(base, topInfo.operand, 0, true, in.name, isType)
528+
atPos(startOffset(od), topInfo.offset) {
529+
PostfixOp(od, topInfo.operator)
530+
}
528531
}
532+
else recur(operand())
529533
}
530-
top = operand()
531-
}
532-
reduceStack(base, top, 0, true, in.name, isType)
534+
else if (in.token == WITH) {
535+
val top1 = reduceStack(base, top, minInfixPrec, leftAssoc = true, nme.WITHkw, isType)
536+
assert(opStack `eq` base)
537+
val app = atPos(startOffset(top1), in.offset) {
538+
in.nextToken()
539+
val args = if (in.token == LPAREN) parArgumentExprs() else operand() :: Nil
540+
Apply(top, args)
541+
}
542+
app.pushAttachment(WithApply, ())
543+
recur(app)
544+
}
545+
else reduceStack(base, top, minPrec, leftAssoc = true, in.name, isType)
546+
547+
recur(first)
533548
}
534549

535550
/* -------- IDENTIFIERS AND LITERALS ------------------------------------------- */
@@ -1340,6 +1355,7 @@ object Parsers {
13401355
/** PostfixExpr ::= InfixExpr [id [nl]]
13411356
* InfixExpr ::= PrefixExpr
13421357
* | InfixExpr id [nl] InfixExpr
1358+
* | InfixExpr ‘with’ (InfixExpr | ParArgumentExprs)
13431359
*/
13441360
def postfixExpr(): Tree =
13451361
infixOps(prefixExpr(), canStartExpressionTokens, prefixExpr, maybePostfix = true)
@@ -1917,27 +1933,27 @@ object Parsers {
19171933
def typeParamClauseOpt(ownerKind: ParamOwner.Value): List[TypeDef] =
19181934
if (in.token == LBRACKET) typeParamClause(ownerKind) else Nil
19191935

1920-
/** ClsParamClauses ::= {ClsParamClause} [[nl] `(' [FunArgMods] ClsParams `)']
1921-
* ClsParamClause ::= [nl] `(' [`erased'] [ClsParams] ')'
1936+
/** ClsParamClause ::= [nl | ‘with’] `(' [FunArgMods] [ClsParams] ')'
19221937
* ClsParams ::= ClsParam {`' ClsParam}
19231938
* ClsParam ::= {Annotation} [{Modifier} (`val' | `var') | `inline'] Param
1924-
* DefParamClauses ::= {DefParamClause} [[nl] `(' [FunArgMods] DefParams `)']
1925-
* DefParamClause ::= [nl] `(' [DefParams] ')'
1939+
* DefParamClause ::= [nl | ‘with’] `(' [FunArgMods] [DefParams] ')'
19261940
* ExtParamClause ::= [nl] ‘(’ ‘this’ DefParam ‘)’
19271941
* DefParams ::= DefParam {`,' DefParam}
19281942
* DefParam ::= {Annotation} [`inline'] Param
19291943
* Param ::= id `:' ParamType [`=' Expr]
19301944
*
1931-
* @return The parameter definitions, and whether leading parameter is tagged `this`
1945+
* @return the list of parameter definitions, and whether this is an extension clause
19321946
*/
1933-
def paramClauses(ofClass: Boolean = false, ofCaseClass: Boolean = false, ofRegularMethod: Boolean = false): (List[List[ValDef]], Boolean) = {
1934-
var imods: Modifiers = EmptyModifiers
1947+
def paramClause(ofClass: Boolean,
1948+
ofCaseClass: Boolean,
1949+
ofRegularMethod: Boolean,
1950+
firstClause: Boolean,
1951+
impliedMods: Modifiers): (List[ValDef], Boolean) = {
19351952
var implicitOffset = -1 // use once
1936-
var firstClause = true
1937-
var isExtension = false
1938-
def param(): ValDef = {
1953+
1954+
def param(impliedMods: Modifiers): ValDef = {
19391955
val start = in.offset
1940-
var mods = annotsAsMods()
1956+
var mods = impliedMods.withAnnotations(annotations())
19411957
if (ofClass) {
19421958
mods = addFlag(modifiers(start = mods), ParamAccessor)
19431959
mods =
@@ -1951,7 +1967,7 @@ object Parsers {
19511967
addMod(mods, mod)
19521968
}
19531969
else {
1954-
if (!(mods.flags &~ (ParamAccessor | Inline)).isEmpty)
1970+
if (!(mods.flags &~ (ParamAccessor | Inline | impliedMods.flags)).isEmpty)
19551971
syntaxError("`val' or `var' expected")
19561972
if (firstClause && ofCaseClass) mods
19571973
else mods | PrivateLocal
@@ -1975,46 +1991,64 @@ object Parsers {
19751991
mods = mods.withPos(mods.pos.union(Position(implicitOffset, implicitOffset)))
19761992
implicitOffset = -1
19771993
}
1978-
for (imod <- imods.mods) mods = addMod(mods, imod)
19791994
ValDef(name, tpt, default).withMods(mods)
19801995
}
19811996
}
1982-
def paramClause(): List[ValDef] = inParens {
1983-
if (in.token == RPAREN) Nil
1997+
1998+
// begin paramClause
1999+
inParens {
2000+
if (in.token == RPAREN)
2001+
(Nil, false)
19842002
else if (in.token == THIS && firstClause && ofRegularMethod) {
19852003
in.nextToken()
1986-
isExtension = true
1987-
param() :: Nil
2004+
(param(impliedMods) :: Nil, true)
19882005
}
19892006
else {
1990-
def funArgMods(): Unit = {
1991-
if (in.token == IMPLICIT) {
2007+
def funArgMods(mods: Modifiers): Modifiers =
2008+
if (in.token == IMPLICIT && !impliedMods.is(Contextual)) {
19922009
implicitOffset = in.offset
1993-
imods = addMod(imods, atPos(accept(IMPLICIT)) { Mod.Implicit() })
1994-
funArgMods()
1995-
} else if (in.token == ERASED) {
1996-
imods = addMod(imods, atPos(accept(ERASED)) { Mod.Erased() })
1997-
funArgMods()
2010+
funArgMods(addMod(mods, atPos(accept(IMPLICIT)) { Mod.Implicit() }))
19982011
}
1999-
}
2000-
funArgMods()
2012+
else if (in.token == ERASED)
2013+
funArgMods(addMod(mods, atPos(accept(ERASED)) { Mod.Erased() }))
2014+
else mods
20012015

2002-
val clause = commaSeparated(() => param())
2016+
val impliedMods1 = funArgMods(impliedMods)
2017+
val clause = commaSeparated(() => param(impliedMods1))
20032018
checkVarArgsRules(clause)
2004-
clause
2019+
(clause, false)
20052020
}
20062021
}
2007-
def clauses(): List[List[ValDef]] = {
2008-
newLineOptWhenFollowedBy(LPAREN)
2009-
if (in.token == LPAREN) {
2010-
imods = EmptyModifiers
2011-
paramClause() :: {
2012-
firstClause = false
2013-
if (imods is Implicit) Nil else clauses()
2022+
}
2023+
2024+
/** ClsParamClauses ::= {ClsParamClause}
2025+
* DefParamClauses ::= {DefParamClause}
2026+
*
2027+
* @return The parameter definitions, and whether leading parameter is tagged `this`
2028+
*/
2029+
def paramClauses(ofClass: Boolean = false,
2030+
ofCaseClass: Boolean = false,
2031+
ofRegularMethod: Boolean = false,
2032+
ofWitness: Boolean = false): (List[List[ValDef]], Boolean) = {
2033+
def recur(firstClause: Boolean): (List[List[ValDef]], Boolean) = {
2034+
val impliedMods =
2035+
if (in.token == WITH) {
2036+
in.nextToken()
2037+
Modifiers(Contextual | Implicit)
20142038
}
2015-
} else Nil
2039+
else EmptyModifiers
2040+
newLineOptWhenFollowedBy(LPAREN)
2041+
if (impliedMods.is(Contextual) || in.token == LPAREN && !ofWitness) {
2042+
val (params, isExtension) =
2043+
paramClause(ofClass, ofCaseClass, ofRegularMethod, firstClause, impliedMods)
2044+
val lastClause =
2045+
params.nonEmpty && params.head.mods.flags.is(Implicit, butNot = Contextual)
2046+
val paramss = if (lastClause) Nil else recur(firstClause = false)._1
2047+
(params :: paramss, isExtension)
2048+
}
2049+
else (Nil, false)
20162050
}
2017-
(clauses(), isExtension)
2051+
recur(firstClause = true)
20182052
}
20192053

20202054
/* -------- DEFS ------------------------------------------- */
@@ -2403,15 +2437,15 @@ object Parsers {
24032437
Template(constr, parents, EmptyValDef, Nil)
24042438
}
24052439

2406-
/** WitnessDef ::= [id] [DefTypeParamClause] [‘for’ [ConstrApps]] TemplateBody
2407-
* | id [DefTypeParamClause] ‘for’ Type [‘=’ Expr]
2440+
/** WitnessDef ::= [id] WitnessParams [‘for’ ConstrApps] [TemplateBody]
2441+
* | id WitnessParams ‘:’ Type ‘=’ Expr
2442+
* | id ‘=’ Expr
2443+
* WitnessParams ::= [DefTypeParamClause] {‘with’ ‘(’ [DefParams] ‘)}
24082444
*/
24092445
def witnessDef(start: Offset, mods: Modifiers, witnessMod: Mod) = atPos(start, nameStart) {
24102446
val name = if (isIdent) ident() else EmptyTermName
24112447
val tparams = typeParamClauseOpt(ParamOwner.Def)
2412-
val (vparamss, _) = paramClauses()
2413-
for (vparams <- vparamss; vparam <- vparams)
2414-
if (!vparam.mods.is(Implicit)) syntaxError("witness can only have implicit parameters", vparam.pos)
2448+
val (vparamss, _) = paramClauses(ofWitness = true)
24152449
val parents =
24162450
if (in.token == FOR) {
24172451
in.nextToken()

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ object Applications {
6161
def productSelectorTypes(tp: Type, errorPos: Position = NoPosition)(implicit ctx: Context): List[Type] = {
6262
def tupleSelectors(n: Int, tp: Type): List[Type] = {
6363
val sel = extractorMemberType(tp, nme.selectorName(n), errorPos)
64-
// extractorMemberType will return NoType if this is the tail of tuple with an unknown tail
64+
// extractorMemberType will return NoType if this is the tail of tuple with an unknown tail
6565
// such as `Int *: T` where `T <: Tuple`.
6666
if (sel.exists) sel :: tupleSelectors(n + 1, tp) else Nil
6767
}
@@ -743,6 +743,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
743743
* Block node.
744744
*/
745745
def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = {
746+
val isContextual = tree.getAttachment(untpd.WithApply).nonEmpty
746747

747748
def realApply(implicit ctx: Context): Tree = track("realApply") {
748749
val originalProto = new FunProto(tree.args, IgnoredProto(pt))(this)(argCtx(tree))

compiler/test/dotc/run-test-pickling.blacklist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ t8133
99
t8133b
1010
tuples1.scala
1111
tuples1a.scala
12+
witnesses.scala
13+
witnesses-anonymous.scala

docs/docs/internals/syntax.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -267,16 +267,15 @@ HkTypeParamClause ::= ‘[’ HkTypeParam {‘,’ HkTypeParam} ‘]’
267267
HkTypeParam ::= {Annotation} [‘+’ | ‘-’] (Id[HkTypeParamClause] | ‘_’)
268268
TypeBounds
269269
270-
ClsParamClauses ::= {ClsParamClause} [[nl] ‘(’ [FunArgMods] ClsParams ‘)’]
271-
ClsParamClause ::= [nl | ‘with’] ‘(’ [ClsParams] ‘)’
270+
ClsParamClauses ::= {ClsParamClause}
271+
ClsParamClause ::= [nl | ‘with’] ‘(’ [[FunArgMods] ClsParams] ‘)’
272272
ClsParams ::= ClsParam {‘,’ ClsParam}
273273
ClsParam ::= {Annotation} ValDef(mods, id, tpe, expr) -- point of mods on val/var
274274
[{Modifier} (‘val’ | ‘var’) | ‘inline’] Param
275275
Param ::= id ‘:’ ParamType [‘=’ Expr]
276276
| INT
277-
278-
DefParamClauses ::= {DefParamClause} [[nl] ‘(’ [FunArgMods] DefParams ‘)’]
279-
DefParamClause ::= [nl | ‘with’] ‘(’ [DefParams] ‘)’
277+
DefParamClauses ::= {DefParamClause}
278+
DefParamClause ::= [nl | ‘with’] ‘(’ [FunArgMods] [DefParams] ‘)’
280279
ExtParamClause ::= [nl] ‘(’ ‘this’ DefParam ‘)’
281280
DefParams ::= DefParam {‘,’ DefParam}
282281
DefParam ::= {Annotation} [‘inline’] Param ValDef(mods, id, tpe, expr) -- point of mods at id.

docs/docs/reference/witnesses/witness-params.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def maximum[T](xs: List[T]) with (cmp: Ord[T]): T =
2121
xs.reduceLeft((x, y) => if (x < y) y else x)
2222

2323
def descending[T] with (asc: Ord[T]): Ord[T] = new Ord[T] {
24-
def compareTo(this x: Int)(y: Int) = asc.compareTo(y)(x)
24+
def compareTo(this x: T)(y: T) = asc.compareTo(y)(x)
2525
}
2626

2727
def minimum[T](xs: List[T]) with (cmp: Ord[T]) =

docs/docs/reference/witnesses/witnesses.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ trait Convertible[From, To] {
9696
}
9797

9898
witness [From, To] with (c: Convertible[From, To]) for Convertible[List[From], List[To]] {
99-
def convert (this x: ListFrom]): List[To] = x.map(c.convert)
99+
def convert (this x: List[From]): List[To] = x.map(c.convert)
100100
}
101101
```
102102

0 commit comments

Comments
 (0)