Skip to content

Drop "extended with" syntax #8009

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
Jan 16, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,6 @@ object StdNames {
val eval: N = "eval"
val eqlAny: N = "eqlAny"
val ex: N = "ex"
val extended: N = "extended"
val extension: N = "extension"
val experimental: N = "experimental"
val f: N = "f"
Expand Down
152 changes: 74 additions & 78 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -913,7 +913,10 @@ object Parsers {
lookahead.nextToken()
if lookahead.token == IDENTIFIER then
lookahead.nextToken()
lookahead.token == COLON
if lookahead.token == COLON then
lookahead.nextToken()
!lookahead.isAfterLineEnd
else false
else false
else false

Expand Down Expand Up @@ -943,7 +946,10 @@ object Parsers {
lookahead.nextToken()
while lookahead.token == LPAREN || lookahead.token == LBRACKET do
lookahead.skipParens()
lookahead.token == COLON || lookahead.token == SUBTYPE
if lookahead.token == COLON then
lookahead.nextToken()
!lookahead.isAfterLineEnd
else lookahead.token == SUBTYPE

def followingIsExtension() =
val lookahead = in.LookaheadScanner()
Expand Down Expand Up @@ -1304,7 +1310,12 @@ object Parsers {
}

def possibleTemplateStart(isNew: Boolean = false): Unit =
if in.token == WITH then
in.observeColonEOL()
if in.token == COLONEOL then
in.nextToken()
if in.token != INDENT then
syntaxError(i"indented definitions expected")
else if in.token == WITH then
in.nextToken()
if in.token != LBRACE && in.token != INDENT then
syntaxError(i"indented definitions or `{` expected")
Expand Down Expand Up @@ -1458,7 +1469,7 @@ object Parsers {
def infixTypeRest(t: Tree): Tree =
infixOps(t, canStartTypeTokens, refinedType, isType = true, isOperator = !isPostfixStar)

/** RefinedType ::= WithType {[nl | `with'] Refinement}
/** RefinedType ::= WithType {[nl] Refinement}
*/
val refinedType: () => Tree = () => refinedTypeRest(withType())

Expand Down Expand Up @@ -2082,7 +2093,8 @@ object Parsers {
}
else simpleExpr()

/** SimpleExpr ::= ‘new’ (ConstrApp [[‘with’] TemplateBody] | TemplateBody)
/** SimpleExpr ::= ‘new’ ConstrApp {`with` ConstrApp} [TemplateBody]
* | ‘new’ TemplateBody
* | BlockExpr
* | ‘$’ ‘{’ Block ‘}’
* | Quoted
Expand Down Expand Up @@ -2177,7 +2189,7 @@ object Parsers {
}
}

/** SimpleExpr ::= ‘new’ ConstrApp {`with` ConstrApp} [[‘with’] TemplateBody]
/** SimpleExpr ::= ‘new’ ConstrApp {`with` ConstrApp} [TemplateBody]
* | ‘new’ TemplateBody
*/
def newExpr(): Tree =
Expand Down Expand Up @@ -3352,7 +3364,7 @@ object Parsers {
syntaxError(s"Only access modifiers are allowed on enum $where")
mods1

/** EnumDef ::= id ClassConstr InheritClauses [‘with’] EnumBody
/** EnumDef ::= id ClassConstr InheritClauses EnumBody
*/
def enumDef(start: Offset, mods: Modifiers): TypeDef = atSpan(start, nameStart) {
val mods1 = checkAccessOnly(mods, "definitions")
Expand Down Expand Up @@ -3415,8 +3427,7 @@ object Parsers {
}

/** GivenDef ::= [GivenSig (‘:’ | <:)] {FunArgTypes ‘=>’} AnnotType ‘=’ Expr
* | [GivenSig ‘:’] {FunArgTypes ‘=>’} ConstrApps [[‘with’] TemplateBody]
* | [id ‘:’] ExtParamClause {GivenParamClause} ‘extended’ ‘with’ ExtMethods
* | [GivenSig ‘:’] {FunArgTypes ‘=>’} ConstrApps [TemplateBody]
* GivenSig ::= [id] [DefTypeParamClause] {GivenParamClause}
* ExtParamClause ::= [DefTypeParamClause] DefParamClause
* ExtMethods ::= [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’
Expand Down Expand Up @@ -3458,83 +3469,68 @@ object Parsers {
if in.token == LPAREN && followingIsParamOrGivenType()
then paramClauses() // todo: ONLY admit a single paramClause
else Nil
val isExtension = isIdent(nme.extended)
def checkAllGivens(vparamss: List[List[ValDef]], what: String) =
vparamss.foreach(_.foreach(vparam =>
if !vparam.mods.is(Given) then syntaxError(em"$what must be `given`", vparam.span)))
if isExtension then
if !name.isEmpty && !hasLabel then
syntaxError(em"name $name of extension clause must be followed by `:`", nameStart)
vparamss match
case (vparam :: Nil) :: vparamss1 if !vparam.mods.is(Given) =>
checkAllGivens(vparamss1, "follow-on parameter in extension clause")
case _ =>
syntaxError("extension clause must start with a single regular parameter", paramsStart)
in.nextToken()
accept(WITH)
val (self, stats) = templateBody()
stats.foreach(checkExtensionMethod(tparams, _))
ModuleDef(name, Template(makeConstructor(tparams, vparamss), Nil, Nil, self, stats))
else
def makeGiven(params: List[ValDef]): List[ValDef] =
params.map(param => param.withMods(param.mods | Given))
def conditionalParents(): List[Tree] =
accept(ARROW)
if in.token == LPAREN && followingIsParam() then
vparamss = vparamss :+ makeGiven(paramClause(vparamss.flatten.length))
conditionalParents()
else
val constrs = constrApps(commaOK = true, templateCanFollow = true)
if in.token == ARROW && constrs.forall(_.isType) then
vparamss = vparamss
:+ typesToGivenParams(constrs, ofClass = false, vparamss.flatten.length)
conditionalParents()
else constrs

val isConditional =
in.token == ARROW
&& vparamss.length == 1
&& (hasLabel || name.isEmpty && tparams.isEmpty)
if !isConditional then checkAllGivens(vparamss, "parameter of given instance")
val parents =
if in.token == SUBTYPE && !hasLabel then
if !mods.is(Inline) then
syntaxError("`<:` is only allowed for given with `inline` modifier")
in.nextToken()
TypeBoundsTree(EmptyTree, annotType()) :: Nil
else if isConditional then
vparamss = vparamss.map(makeGiven)
def makeGiven(params: List[ValDef]): List[ValDef] =
params.map(param => param.withMods(param.mods | Given))
def conditionalParents(): List[Tree] =
accept(ARROW)
if in.token == LPAREN && followingIsParam() then
vparamss = vparamss :+ makeGiven(paramClause(vparamss.flatten.length))
conditionalParents()
else
val constrs = constrApps(commaOK = true, templateCanFollow = true)
if in.token == ARROW && constrs.forall(_.isType) then
vparamss = vparamss
:+ typesToGivenParams(constrs, ofClass = false, vparamss.flatten.length)
conditionalParents()
else
if !hasLabel && !(name.isEmpty && tparams.isEmpty && vparamss.isEmpty) then
accept(COLON)
val constrs = constrApps(commaOK = true, templateCanFollow = true)
if in.token == ARROW && vparamss.isEmpty && constrs.forall(_.isType) then
vparamss = typesToGivenParams(constrs, ofClass = false, 0) :: Nil
conditionalParents()
else
constrs

if in.token == EQUALS && parents.length == 1 && parents.head.isType then
else constrs

val isConditional =
in.token == ARROW
&& vparamss.length == 1
&& (hasLabel || name.isEmpty && tparams.isEmpty)
if !isConditional then checkAllGivens(vparamss, "parameter of given instance")
val parents =
if in.token == SUBTYPE && !hasLabel then
if !mods.is(Inline) then
syntaxError("`<:` is only allowed for given with `inline` modifier")
in.nextToken()
mods1 |= Final
DefDef(name, tparams, vparamss, parents.head, subExpr())
TypeBoundsTree(EmptyTree, annotType()) :: Nil
else if isConditional then
vparamss = vparamss.map(makeGiven)
conditionalParents()
else
parents match
case TypeBoundsTree(_, _) :: _ => syntaxError("`=` expected")
case _ =>
possibleTemplateStart()
val tparams1 = tparams.map(tparam => tparam.withMods(tparam.mods | PrivateLocal))
val vparamss1 = vparamss.map(_.map(vparam =>
vparam.withMods(vparam.mods &~ Param | ParamAccessor | PrivateLocal)))
val templ = templateBodyOpt(makeConstructor(tparams1, vparamss1), parents, Nil)
if tparams.isEmpty && vparamss.isEmpty then ModuleDef(name, templ)
else TypeDef(name.toTypeName, templ)
if !hasLabel && !(name.isEmpty && tparams.isEmpty && vparamss.isEmpty) then
accept(COLON)
val constrs = constrApps(commaOK = true, templateCanFollow = true)
if in.token == ARROW && vparamss.isEmpty && constrs.forall(_.isType) then
vparamss = typesToGivenParams(constrs, ofClass = false, 0) :: Nil
conditionalParents()
else
constrs

if in.token == EQUALS && parents.length == 1 && parents.head.isType then
in.nextToken()
mods1 |= Final
DefDef(name, tparams, vparamss, parents.head, subExpr())
else
parents match
case TypeBoundsTree(_, _) :: _ => syntaxError("`=` expected")
case _ =>
possibleTemplateStart()
val tparams1 = tparams.map(tparam => tparam.withMods(tparam.mods | PrivateLocal))
val vparamss1 = vparamss.map(_.map(vparam =>
vparam.withMods(vparam.mods &~ Param | ParamAccessor | PrivateLocal)))
val templ = templateBodyOpt(makeConstructor(tparams1, vparamss1), parents, Nil)
if tparams.isEmpty && vparamss.isEmpty then ModuleDef(name, templ)
else TypeDef(name.toTypeName, templ)
}
finalizeDef(gdef, mods1, start)
}

/** ExtensionDef ::= [id] ‘of’ ExtParamClause {GivenParamClause} ‘with’ ExtMethods
/** ExtensionDef ::= [id] ‘on’ ExtParamClause {GivenParamClause} ExtMethods
*/
def extensionDef(start: Offset, mods: Modifiers): ModuleDef =
in.nextToken()
Expand Down Expand Up @@ -3580,7 +3576,7 @@ object Parsers {
else Nil
t :: ts

/** Template ::= InheritClauses [[‘with’] TemplateBody]
/** Template ::= InheritClauses [TemplateBody]
* InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}]
*/
def template(constr: DefDef, isEnum: Boolean = false): Template = {
Expand Down Expand Up @@ -3650,7 +3646,7 @@ object Parsers {
case x: RefTree => atSpan(start, pointOffset(pkg))(PackageDef(x, stats))
}

/** Packaging ::= package QualId [nl | `with'] `{' TopStatSeq `}'
/** Packaging ::= package QualId [nl] `{' TopStatSeq `}'
*/
def packaging(start: Int): Tree = {
val pkg = qualId()
Expand Down
32 changes: 19 additions & 13 deletions compiler/src/dotty/tools/dotc/parsing/Scanners.scala
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,13 @@ object Scanners {
|Previous indent : $lastWidth
|Latest indent : $nextWidth"""

def observeColonEOL(): Unit =
if token == COLON then
lookahead()
val atEOL = isAfterLineEnd
reset()
if atEOL then token = COLONEOL

def observeIndented(): Unit =
if indentSyntax && isNewLine then
val nextWidth = indentWidth(next.offset)
Expand All @@ -575,19 +582,21 @@ object Scanners {
insert(OUTDENT, offset)
case _ =>

def lookahead() = {
prev.copyFrom(this)
lastOffset = lastCharOffset
fetchToken()
}

def reset() = {
next.copyFrom(this)
this.copyFrom(prev)
}

/** - Join CASE + CLASS => CASECLASS, CASE + OBJECT => CASEOBJECT, SEMI + ELSE => ELSE, COLON + <EOL> => COLONEOL
* - Insert missing OUTDENTs at EOF
*/
def postProcessToken(): Unit = {
def lookahead() = {
prev.copyFrom(this)
lastOffset = lastCharOffset
fetchToken()
}
def reset() = {
next.copyFrom(this)
this.copyFrom(prev)
}
def fuse(tok: Int) = {
token = tok
offset = prev.offset
Expand All @@ -611,10 +620,7 @@ object Scanners {
/* skip the trailing comma */
} else reset()
case COLON =>
lookahead()
val atEOL = isAfterLineEnd
reset()
if (colonSyntax && atEOL) token = COLONEOL
if colonSyntax then observeColonEOL()
case EOF | RBRACE =>
currentRegion match {
case r: Indented if !r.isOutermost =>
Expand Down
16 changes: 8 additions & 8 deletions docs/docs/internals/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ FunArgTypes ::= InfixType
TypedFunParam ::= id ‘:’ Type
MatchType ::= InfixType `match` ‘{’ TypeCaseClauses ‘}’
InfixType ::= RefinedType {id [nl] RefinedType} InfixOp(t1, op, t2)
RefinedType ::= WithType {[nl | ‘with’] Refinement} RefinedTypeTree(t, ds)
RefinedType ::= WithType {[nl] Refinement} RefinedTypeTree(t, ds)
WithType ::= AnnotType {‘with’ AnnotType} (deprecated)
AnnotType ::= SimpleType {Annotation} Annotated(t, annot)
SimpleType ::= SimpleType TypeArgs AppliedTypeTree(t, args)
Expand Down Expand Up @@ -214,7 +214,7 @@ SimpleExpr ::= Path
| ‘$’ ‘{’ Block ‘}’
| Quoted
| quoteId // only inside splices
| ‘new’ ConstrApp {‘with’ ConstrApp} [[‘with’] TemplateBody] New(constr | templ)
| ‘new’ ConstrApp {‘with’ ConstrApp} [TemplateBody] New(constr | templ)
| ‘new’ TemplateBody
| ‘(’ ExprsInParens ‘)’ Parens(exprs)
| SimpleExpr ‘.’ id Select(expr, id)
Expand Down Expand Up @@ -383,16 +383,16 @@ ClassDef ::= id ClassConstr [Template]
ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses with DefDef(_, <init>, Nil, vparamss, EmptyTree, EmptyTree) as first stat
ConstrMods ::= {Annotation} [AccessModifier]
ObjectDef ::= id [Template] ModuleDef(mods, name, template) // no constructor
EnumDef ::= id ClassConstr InheritClauses [‘with’] EnumBody EnumDef(mods, name, tparams, template)
EnumDef ::= id ClassConstr InheritClauses EnumBody EnumDef(mods, name, tparams, template)
GivenDef ::= [GivenSig (‘:’ | <:)] {FunArgTypes ‘=>’}
AnnotType ‘=’ Expr
| [GivenSig ‘:’] {FunArgTypes ‘=>’}
ConstrApps [[‘with’] TemplateBody]
ConstrApps [TemplateBody]
GivenSig ::= [id] [DefTypeParamClause] {GivenParamClause}
ExtensionDef ::= [id] ‘of’ ExtParamClause {GivenParamClause} ExtMethods
ExtensionDef ::= [id] ‘on’ ExtParamClause {GivenParamClause} ExtMethods
ExtMethods ::= [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’
ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’
Template ::= InheritClauses [[‘with’] TemplateBody] Template(constr, parents, self, stats)
Template ::= InheritClauses [TemplateBody] Template(constr, parents, self, stats)
InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}]
ConstrApps ::= ConstrApp {(‘,’ | ‘with’) ConstrApp}
ConstrApp ::= AnnotType {ParArgumentExprs} Apply(tp, args)
Expand All @@ -410,7 +410,7 @@ TemplateStat ::= Import
SelfType ::= id [‘:’ InfixType] ‘=>’ ValDef(_, name, tpt, _)
| ‘this’ ‘:’ InfixType ‘=>’

EnumBody ::= [nl | ‘with’] ‘{’ [SelfType] EnumStat {semi EnumStat} ‘}’
EnumBody ::= [nl] ‘{’ [SelfType] EnumStat {semi EnumStat} ‘}’
EnumStat ::= TemplateStat
| {Annotation [nl]} {Modifier} EnumCase
EnumCase ::= ‘case’ (id ClassConstr [‘extends’ ConstrApps]] | ids)
Expand All @@ -422,7 +422,7 @@ TopStat ::= Import
| Packaging
| PackageObject
|
Packaging ::= ‘package’ QualId [nl | ‘with’] ‘{’ TopStatSeq ‘}’ Package(qid, stats)
Packaging ::= ‘package’ QualId [nl] ‘{’ TopStatSeq ‘}’ Package(qid, stats)
PackageObject ::= ‘package’ ‘object’ ObjectDef object with package in mods.

CompilationUnit ::= {‘package’ QualId semi} TopStatSeq Package(qid, stats)
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/reference/other-new-features/opaques.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ object Logarithms {
}

// Extension methods define opaque types' public APIs
given logarithmOps: (x: Logarithm) extended with {
extension logarithmOps on (x: Logarithm) {
def toDouble: Double = math.exp(x)
def + (y: Logarithm): Logarithm = Logarithm(math.exp(x) + math.exp(y))
def * (y: Logarithm): Logarithm = x + y
Expand Down
Loading