Skip to content

Commit 98b44f6

Browse files
committed
New syntax for given defs
given [A: Ord] => A is Ord: ... given [A: Ord] => A is Ord as listOrd: ...
1 parent 42d74c0 commit 98b44f6

File tree

4 files changed

+201
-16
lines changed

4 files changed

+201
-16
lines changed

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

Lines changed: 57 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -968,19 +968,31 @@ object Parsers {
968968
* i.e. an identifier followed by type and value parameters, followed by `:`?
969969
* @pre The current token is an identifier
970970
*/
971-
def followingIsGivenSig() =
971+
def followingIsOldStyleGivenSig() =
972972
val lookahead = in.LookaheadScanner()
973973
if lookahead.isIdent then
974974
lookahead.nextToken()
975+
var paramsSeen = false
975976
def skipParams(): Unit =
976977
if lookahead.token == LPAREN || lookahead.token == LBRACKET then
978+
paramsSeen = true
977979
lookahead.skipParens()
978980
skipParams()
979981
else if lookahead.isNewLine then
980982
lookahead.nextToken()
981983
skipParams()
982984
skipParams()
983985
lookahead.isColon
986+
&& {
987+
!in.featureEnabled(Feature.modularity)
988+
|| { // with modularity language import, a `:` at EOL after an identifier represents a single identifier given
989+
// Example:
990+
// given C:
991+
// def f = ...
992+
lookahead.nextToken()
993+
!lookahead.isAfterLineEnd
994+
}
995+
}
984996

985997
def followingIsExtension() =
986998
val next = in.lookahead.token
@@ -1793,7 +1805,9 @@ object Parsers {
17931805

17941806
def infixTypeRest(t: Tree, operand: Location => Tree = refinedTypeFn): Tree =
17951807
infixOps(t, canStartInfixTypeTokens, operand, Location.ElseWhere, ParseKind.Type,
1796-
isOperator = !followingIsVararg() && !isPureArrow
1808+
isOperator = !followingIsVararg()
1809+
&& !isPureArrow
1810+
&& !(isIdent(nme.as) && in.featureEnabled(Feature.modularity))
17971811
&& nextCanFollowOperator(canStartInfixTypeTokens))
17981812

17991813
/** RefinedType ::= WithType {[nl] Refinement} [`^` CaptureSet]
@@ -4079,15 +4093,30 @@ object Parsers {
40794093
syntaxError(em"extension clause can only define methods", stat.span)
40804094
}
40814095

4082-
/** GivenDef ::= [GivenSig] (GivenType [‘=’ Expr] | StructuralInstance)
4083-
* GivenSig ::= [id] [DefTypeParamClause] {UsingParamClauses} ‘:’
4084-
* GivenType ::= AnnotType1 {id [nl] AnnotType1}
4096+
/** GivenDef ::= OldGivenDef | NewGivenDef
4097+
* OldGivenDef ::= [OldGivenSig] (GivenType [‘=’ Expr] | StructuralInstance)
4098+
* OldGivenSig ::= [id] [DefTypeParamClause] {UsingParamClauses} ‘:’
40854099
* StructuralInstance ::= ConstrApp {‘with’ ConstrApp} [‘with’ WithTemplateBody]
4100+
*
4101+
* NewGivenDef ::= [GivenConditional '=>'] NewGivenSig
4102+
* GivenConditional ::= [DefTypeParamClause | UsingParamClause] {UsingParamClause}
4103+
* NewGivenSig ::= GivenType ['as' id] ([‘=’ Expr] | TemplateBody)
4104+
* | ConstrApps ['as' id] TemplateBody
4105+
*
4106+
* GivenType ::= AnnotType1 {id [nl] AnnotType1}
40864107
*/
40874108
def givenDef(start: Offset, mods: Modifiers, givenMod: Mod) = atSpan(start, nameStart) {
40884109
var mods1 = addMod(mods, givenMod)
40894110
val nameStart = in.offset
4090-
val name = if isIdent && followingIsGivenSig() then ident() else EmptyTermName
4111+
var name = if isIdent && followingIsOldStyleGivenSig() then ident() else EmptyTermName
4112+
var newSyntaxAllowed = in.featureEnabled(Feature.modularity)
4113+
4114+
def moreConstrApps() =
4115+
if newSyntaxAllowed && in.token == COMMA then
4116+
in.nextToken()
4117+
constrApps()
4118+
else // need to be careful with last `with`
4119+
withConstrApps()
40914120

40924121
// TODO Change syntax description
40934122
def adjustDefParams(paramss: List[ParamClause]): List[ParamClause] =
@@ -4106,14 +4135,24 @@ object Parsers {
41064135
else Nil
41074136
newLinesOpt()
41084137
val noParams = tparams.isEmpty && vparamss.isEmpty
4109-
if !(name.isEmpty && noParams) then acceptColon()
4138+
if !(name.isEmpty && noParams) then
4139+
if in.isColon then
4140+
newSyntaxAllowed = false
4141+
in.nextToken()
4142+
else if newSyntaxAllowed then accept(ARROW)
4143+
else acceptColon()
41104144
val parents =
41114145
if isSimpleLiteral then
41124146
rejectWildcardType(annotType()) :: Nil
41134147
else constrApp() match
4114-
case parent: Apply => parent :: withConstrApps()
4115-
case parent if in.isIdent => infixTypeRest(parent, _ => annotType1()) :: Nil
4116-
case parent => parent :: withConstrApps()
4148+
case parent: Apply => parent :: moreConstrApps()
4149+
case parent if in.isIdent =>
4150+
infixTypeRest(parent, _ => annotType1()) :: Nil
4151+
case parent => parent :: moreConstrApps()
4152+
if newSyntaxAllowed && in.isIdent(nme.as) then
4153+
in.nextToken()
4154+
name = ident()
4155+
41174156
val parentsIsType = parents.length == 1 && parents.head.isType
41184157
if in.token == EQUALS && parentsIsType then
41194158
accept(EQUALS)
@@ -4123,7 +4162,7 @@ object Parsers {
41234162
ValDef(name, parents.head, subExpr())
41244163
else
41254164
DefDef(name, adjustDefParams(joinParams(tparams, vparamss)), parents.head, subExpr())
4126-
else if (isStatSep || isStatSeqEnd) && parentsIsType then
4165+
else if (isStatSep || isStatSeqEnd) && parentsIsType && !newSyntaxAllowed then
41274166
if name.isEmpty then
41284167
syntaxError(em"anonymous given cannot be abstract")
41294168
DefDef(name, adjustDefParams(joinParams(tparams, vparamss)), parents.head, EmptyTree)
@@ -4134,8 +4173,13 @@ object Parsers {
41344173
else vparam
41354174
val constr = makeConstructor(tparams, vparamss1)
41364175
val templ =
4137-
if isStatSep || isStatSeqEnd then Template(constr, parents, Nil, EmptyValDef, Nil)
4138-
else withTemplate(constr, parents)
4176+
if isStatSep || isStatSeqEnd then
4177+
Template(constr, parents, Nil, EmptyValDef, Nil)
4178+
else if !newSyntaxAllowed || in.token == WITH then
4179+
withTemplate(constr, parents)
4180+
else
4181+
possibleTemplateStart()
4182+
templateBodyOpt(constr, parents, Nil)
41394183
if noParams && !mods.is(Inline) then ModuleDef(name, templ)
41404184
else TypeDef(name.toTypeName, templ)
41414185
end gdef

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,5 +123,7 @@ i15525.scala
123123
# alias types at different levels of dereferencing
124124
parsercombinators-givens.scala
125125
parsercombinators-givens-2.scala
126+
parsercombinators-arrow.scala
127+
126128

127129

docs/_docs/internals/syntax.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -460,10 +460,13 @@ ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses
460460
ConstrMods ::= {Annotation} [AccessModifier]
461461
ObjectDef ::= id [Template] ModuleDef(mods, name, template) // no constructor
462462
EnumDef ::= id ClassConstr InheritClauses EnumBody
463-
GivenDef ::= [GivenSig] (GivenType [‘=’ Expr] | StructuralInstance)
464-
GivenSig ::= [id] [DefTypeParamClause] {UsingParamClause} ‘:’ -- one of `id`, `DefTypeParamClause`, `UsingParamClause` must be present
463+
464+
GivenDef ::= [GivenConditional '=>'] GivenSig
465+
GivenConditional ::= [DefTypeParamClause | UsingParamClause] {UsingParamClause}
466+
GivenSig ::= GivenType ['as' id] ([‘=’ Expr] | TemplateBody)
467+
| ConstrApps ['as' id] TemplateBody
465468
GivenType ::= AnnotType1 {id [nl] AnnotType1}
466-
StructuralInstance ::= ConstrApp {‘with’ ConstrApp} [‘with’ WithTemplateBody]
469+
467470
Extension ::= ‘extension’ [DefTypeParamClause] {UsingParamClause}
468471
‘(’ DefTermParam ‘)’ {UsingParamClause} ExtMethods
469472
ExtMethods ::= ExtMethod | [nl] <<< ExtMethod {semi ExtMethod} >>>

tests/pos/typeclasses-arrow0.scala

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
//> using options -language:experimental.modularity -source future
2+
3+
class Common:
4+
5+
trait Ord[A]:
6+
extension (x: A)
7+
def compareTo(y: A): Int
8+
def < (y: A): Boolean = compareTo(y) < 0
9+
def > (y: A): Boolean = compareTo(y) > 0
10+
def <= (y: A): Boolean = compareTo(y) <= 0
11+
def >= (y: A): Boolean = compareTo(y) >= 0
12+
def max(y: A): A = if x < y then y else x
13+
14+
trait Show[A]:
15+
extension (x: A) def show: String
16+
17+
trait SemiGroup[A]:
18+
extension (x: A) def combine(y: A): A
19+
20+
trait Monoid[A] extends SemiGroup[A]:
21+
def unit: A
22+
23+
trait Functor[F[_]]:
24+
extension [A](x: F[A]) def map[B](f: A => B): F[B]
25+
26+
trait Monad[F[_]] extends Functor[F]:
27+
def pure[A](x: A): F[A]
28+
extension [A](x: F[A])
29+
def flatMap[B](f: A => F[B]): F[B]
30+
def map[B](f: A => B) = x.flatMap(f `andThen` pure)
31+
end Common
32+
33+
object Instances extends Common:
34+
35+
given Ord[Int] as intOrd:
36+
extension (x: Int)
37+
def compareTo(y: Int) =
38+
if x < y then -1
39+
else if x > y then +1
40+
else 0
41+
42+
given [T: Ord] => Ord[List[T]]:
43+
extension (xs: List[T]) def compareTo(ys: List[T]): Int = (xs, ys) match
44+
case (Nil, Nil) => 0
45+
case (Nil, _) => -1
46+
case (_, Nil) => +1
47+
case (x :: xs1, y :: ys1) =>
48+
val fst = x.compareTo(y)
49+
if (fst != 0) fst else xs1.compareTo(ys1)
50+
51+
given Monad[List] as listMonad:
52+
extension [A](xs: List[A]) def flatMap[B](f: A => List[B]): List[B] =
53+
xs.flatMap(f)
54+
def pure[A](x: A): List[A] =
55+
List(x)
56+
57+
type Reader[Ctx] = [X] =>> Ctx => X
58+
59+
given [Ctx] => Monad[Reader[Ctx]] as readerMonad:
60+
extension [A](r: Ctx => A) def flatMap[B](f: A => Ctx => B): Ctx => B =
61+
ctx => f(r(ctx))(ctx)
62+
def pure[A](x: A): Ctx => A =
63+
ctx => x
64+
65+
extension (xs: Seq[String])
66+
def longestStrings: Seq[String] =
67+
val maxLength = xs.map(_.length).max
68+
xs.filter(_.length == maxLength)
69+
70+
extension [T](xs: List[T])
71+
def second = xs.tail.head
72+
def third = xs.tail.tail.head
73+
74+
extension [M[_]: Monad, A](xss: M[M[A]])
75+
def flatten: M[A] =
76+
xss.flatMap(identity)
77+
78+
def maximum[T: Ord](xs: List[T]): T =
79+
xs.reduce(_ `max` _)
80+
81+
given [T: Ord] => Ord[T] as descending:
82+
extension (x: T) def compareTo(y: T) = summon[Ord[T]].compareTo(y)(x)
83+
84+
def minimum[T: Ord](xs: List[T]) =
85+
maximum(xs)(using descending)
86+
87+
def test(): Unit =
88+
val xs = List(1, 2, 3)
89+
println(maximum(xs))
90+
println(maximum(xs)(using descending))
91+
println(maximum(xs)(using descending(using intOrd)))
92+
println(minimum(xs))
93+
94+
// Adapted from the Rust by Example book: https://doc.rust-lang.org/rust-by-example/trait.html
95+
//
96+
// lines words chars
97+
// wc Scala: 28 105 793
98+
// wc Rust : 57 193 1466
99+
100+
trait Animal[Self]:
101+
102+
// Associated function signature; `Self` refers to the implementor type.
103+
def apply(name: String): Self
104+
105+
// Method signatures; these will return a string.
106+
extension (self: Self)
107+
def name: String
108+
def noise: String
109+
def talk(): Unit = println(s"$name, $noise")
110+
end Animal
111+
112+
class Sheep(val name: String):
113+
var isNaked = false
114+
def shear() =
115+
if isNaked then
116+
println(s"$name is already naked...")
117+
else
118+
println(s"$name gets a haircut!")
119+
isNaked = true
120+
121+
given Animal[Sheep]:
122+
def apply(name: String) = Sheep(name)
123+
extension (self: Sheep)
124+
def name: String = self.name
125+
def noise: String = if self.isNaked then "baaaaah?" else "baaaaah!"
126+
override def talk(): Unit =
127+
println(s"$name pauses briefly... $noise")
128+
129+
/*
130+
131+
- In a type pattern, A <: T, A >: T, A: T, A: _ are all allowed and mean
132+
T is a fresh type variable (T can start with a capital letter).
133+
- instance definitions
134+
- `as m` syntax in context bounds and instance definitions
135+
136+
*/

0 commit comments

Comments
 (0)