Skip to content

Commit 2f58cbc

Browse files
committed
New syntax for given defs
given [A: Ord] => A is Ord: ... given [A: Ord] => A is Ord as listOrd: ...
1 parent ef71dcb commit 2f58cbc

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
@@ -976,19 +976,31 @@ object Parsers {
976976
* i.e. an identifier followed by type and value parameters, followed by `:`?
977977
* @pre The current token is an identifier
978978
*/
979-
def followingIsGivenSig() =
979+
def followingIsOldStyleGivenSig() =
980980
val lookahead = in.LookaheadScanner()
981981
if lookahead.isIdent then
982982
lookahead.nextToken()
983+
var paramsSeen = false
983984
def skipParams(): Unit =
984985
if lookahead.token == LPAREN || lookahead.token == LBRACKET then
986+
paramsSeen = true
985987
lookahead.skipParens()
986988
skipParams()
987989
else if lookahead.isNewLine then
988990
lookahead.nextToken()
989991
skipParams()
990992
skipParams()
991993
lookahead.isColon
994+
&& {
995+
!in.featureEnabled(Feature.modularity)
996+
|| { // with modularity language import, a `:` at EOL after an identifier represents a single identifier given
997+
// Example:
998+
// given C:
999+
// def f = ...
1000+
lookahead.nextToken()
1001+
!lookahead.isAfterLineEnd
1002+
}
1003+
}
9921004

9931005
def followingIsExtension() =
9941006
val next = in.lookahead.token
@@ -1808,7 +1820,9 @@ object Parsers {
18081820

18091821
def infixTypeRest(t: Tree, operand: Location => Tree = refinedTypeFn): Tree =
18101822
infixOps(t, canStartInfixTypeTokens, operand, Location.ElseWhere, ParseKind.Type,
1811-
isOperator = !followingIsVararg() && !isPureArrow
1823+
isOperator = !followingIsVararg()
1824+
&& !isPureArrow
1825+
&& !(isIdent(nme.as) && in.featureEnabled(Feature.modularity))
18121826
&& nextCanFollowOperator(canStartInfixTypeTokens))
18131827

18141828
/** RefinedType ::= WithType {[nl] Refinement} [`^` CaptureSet]
@@ -4101,15 +4115,30 @@ object Parsers {
41014115
syntaxError(em"extension clause can only define methods", stat.span)
41024116
}
41034117

4104-
/** GivenDef ::= [GivenSig] (GivenType [‘=’ Expr] | StructuralInstance)
4105-
* GivenSig ::= [id] [DefTypeParamClause] {UsingParamClauses} ‘:’
4106-
* GivenType ::= AnnotType1 {id [nl] AnnotType1}
4118+
/** GivenDef ::= OldGivenDef | NewGivenDef
4119+
* OldGivenDef ::= [OldGivenSig] (GivenType [‘=’ Expr] | StructuralInstance)
4120+
* OldGivenSig ::= [id] [DefTypeParamClause] {UsingParamClauses} ‘:’
41074121
* StructuralInstance ::= ConstrApp {‘with’ ConstrApp} [‘with’ WithTemplateBody]
4122+
*
4123+
* NewGivenDef ::= [GivenConditional '=>'] NewGivenSig
4124+
* GivenConditional ::= [DefTypeParamClause | UsingParamClause] {UsingParamClause}
4125+
* NewGivenSig ::= GivenType ['as' id] ([‘=’ Expr] | TemplateBody)
4126+
* | ConstrApps ['as' id] TemplateBody
4127+
*
4128+
* GivenType ::= AnnotType1 {id [nl] AnnotType1}
41084129
*/
41094130
def givenDef(start: Offset, mods: Modifiers, givenMod: Mod) = atSpan(start, nameStart) {
41104131
var mods1 = addMod(mods, givenMod)
41114132
val nameStart = in.offset
4112-
val name = if isIdent && followingIsGivenSig() then ident() else EmptyTermName
4133+
var name = if isIdent && followingIsOldStyleGivenSig() then ident() else EmptyTermName
4134+
var newSyntaxAllowed = in.featureEnabled(Feature.modularity)
4135+
4136+
def moreConstrApps() =
4137+
if newSyntaxAllowed && in.token == COMMA then
4138+
in.nextToken()
4139+
constrApps()
4140+
else // need to be careful with last `with`
4141+
withConstrApps()
41134142

41144143
// TODO Change syntax description
41154144
def adjustDefParams(paramss: List[ParamClause]): List[ParamClause] =
@@ -4128,14 +4157,24 @@ object Parsers {
41284157
else Nil
41294158
newLinesOpt()
41304159
val noParams = tparams.isEmpty && vparamss.isEmpty
4131-
if !(name.isEmpty && noParams) then acceptColon()
4160+
if !(name.isEmpty && noParams) then
4161+
if in.isColon then
4162+
newSyntaxAllowed = false
4163+
in.nextToken()
4164+
else if newSyntaxAllowed then accept(ARROW)
4165+
else acceptColon()
41324166
val parents =
41334167
if isSimpleLiteral then
41344168
rejectWildcardType(annotType()) :: Nil
41354169
else constrApp() match
4136-
case parent: Apply => parent :: withConstrApps()
4137-
case parent if in.isIdent => infixTypeRest(parent, _ => annotType1()) :: Nil
4138-
case parent => parent :: withConstrApps()
4170+
case parent: Apply => parent :: moreConstrApps()
4171+
case parent if in.isIdent =>
4172+
infixTypeRest(parent, _ => annotType1()) :: Nil
4173+
case parent => parent :: moreConstrApps()
4174+
if newSyntaxAllowed && in.isIdent(nme.as) then
4175+
in.nextToken()
4176+
name = ident()
4177+
41394178
val parentsIsType = parents.length == 1 && parents.head.isType
41404179
if in.token == EQUALS && parentsIsType then
41414180
accept(EQUALS)
@@ -4145,7 +4184,7 @@ object Parsers {
41454184
ValDef(name, parents.head, subExpr())
41464185
else
41474186
DefDef(name, adjustDefParams(joinParams(tparams, vparamss)), parents.head, subExpr())
4148-
else if (isStatSep || isStatSeqEnd) && parentsIsType then
4187+
else if (isStatSep || isStatSeqEnd) && parentsIsType && !newSyntaxAllowed then
41494188
if name.isEmpty then
41504189
syntaxError(em"anonymous given cannot be abstract")
41514190
DefDef(name, adjustDefParams(joinParams(tparams, vparamss)), parents.head, EmptyTree)
@@ -4156,8 +4195,13 @@ object Parsers {
41564195
else vparam
41574196
val constr = makeConstructor(tparams, vparamss1)
41584197
val templ =
4159-
if isStatSep || isStatSeqEnd then Template(constr, parents, Nil, EmptyValDef, Nil)
4160-
else withTemplate(constr, parents)
4198+
if isStatSep || isStatSeqEnd then
4199+
Template(constr, parents, Nil, EmptyValDef, Nil)
4200+
else if !newSyntaxAllowed || in.token == WITH then
4201+
withTemplate(constr, parents)
4202+
else
4203+
possibleTemplateStart()
4204+
templateBodyOpt(constr, parents, Nil)
41614205
if noParams && !mods.is(Inline) then ModuleDef(name, templ)
41624206
else TypeDef(name.toTypeName, templ)
41634207
end gdef

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,5 +127,7 @@ i20053b.scala
127127
# alias types at different levels of dereferencing
128128
parsercombinators-givens.scala
129129
parsercombinators-givens-2.scala
130+
parsercombinators-arrow.scala
131+
130132

131133

docs/_docs/internals/syntax.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -467,10 +467,13 @@ ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses
467467
ConstrMods ::= {Annotation} [AccessModifier]
468468
ObjectDef ::= id [Template] ModuleDef(mods, name, template) // no constructor
469469
EnumDef ::= id ClassConstr InheritClauses EnumBody
470-
GivenDef ::= [GivenSig] (GivenType [‘=’ Expr] | StructuralInstance)
471-
GivenSig ::= [id] [DefTypeParamClause] {UsingParamClause} ‘:’ -- one of `id`, `DefTypeParamClause`, `UsingParamClause` must be present
470+
471+
GivenDef ::= [GivenConditional '=>'] GivenSig
472+
GivenConditional ::= [DefTypeParamClause | UsingParamClause] {UsingParamClause}
473+
GivenSig ::= GivenType ['as' id] ([‘=’ Expr] | TemplateBody)
474+
| ConstrApps ['as' id] TemplateBody
472475
GivenType ::= AnnotType1 {id [nl] AnnotType1}
473-
StructuralInstance ::= ConstrApp {‘with’ ConstrApp} [‘with’ WithTemplateBody]
476+
474477
Extension ::= ‘extension’ [DefTypeParamClause] {UsingParamClause}
475478
‘(’ DefTermParam ‘)’ {UsingParamClause} ExtMethods
476479
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)