Skip to content

New syntax for collective extension methods #7917

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 12 commits into from
Jan 13, 2020
47 changes: 21 additions & 26 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -954,18 +954,14 @@ object desugar {
else tree
}

/** Invent a name for an anonympus given of type or template `impl`. */
def inventGivenName(impl: Tree)(implicit ctx: Context): SimpleName =
s"given_${inventName(impl)}".toTermName.asSimpleName

/** The normalized name of `mdef`. This means
* 1. Check that the name does not redefine a Scala core class.
* If it does redefine, issue an error and return a mangled name instead of the original one.
* 2. If the name is missing (this can be the case for instance definitions), invent one instead.
*/
def normalizeName(mdef: MemberDef, impl: Tree)(implicit ctx: Context): Name = {
var name = mdef.name
if (name.isEmpty) name = name.likeSpaced(inventGivenName(impl))
if (name.isEmpty) name = name.likeSpaced(inventGivenOrExtensionName(impl))
if (ctx.owner == defn.ScalaPackageClass && defn.reservedScalaClassNames.contains(name.toTypeName)) {
def kind = if (name.isTypeName) "class" else "object"
ctx.error(em"illegal redefinition of standard $kind $name", mdef.sourcePos)
Expand All @@ -974,27 +970,26 @@ object desugar {
name
}

/** Invent a name for an anonymous instance with template `impl`.
*/
private def inventName(impl: Tree)(implicit ctx: Context): String = impl match {
case impl: Template =>
if (impl.parents.isEmpty)
impl.body.find {
case dd: DefDef if dd.mods.is(Extension) => true
case _ => false
}
match {
case Some(DefDef(name, _, (vparam :: _) :: _, _, _)) =>
s"${name}_of_${inventTypeName(vparam.tpt)}"
case _ =>
ctx.error(i"anonymous instance must implement a type or have at least one extension method", impl.sourcePos)
nme.ERROR.toString
}
else
impl.parents.map(inventTypeName(_)).mkString("_")
case impl: Tree =>
inventTypeName(impl)
}
/** Invent a name for an anonympus given or extension of type or template `impl`. */
def inventGivenOrExtensionName(impl: Tree)(given ctx: Context): SimpleName =
val str = impl match
case impl: Template =>
if impl.parents.isEmpty then
impl.body.find {
case dd: DefDef if dd.mods.is(Extension) => true
case _ => false
}
match
case Some(DefDef(name, _, (vparam :: _) :: _, _, _)) =>
s"extension_${name}_${inventTypeName(vparam.tpt)}"
case _ =>
ctx.error(i"anonymous instance must implement a type or have at least one extension method", impl.sourcePos)
nme.ERROR.toString
else
impl.parents.map(inventTypeName(_)).mkString("given_", "_", "")
case impl: Tree =>
"given_" ++ inventTypeName(impl)
str.toTermName.asSimpleName

private class NameExtractor(followArgs: Boolean) extends UntypedTreeAccumulator[String] {
private def extractArgs(args: List[Tree])(implicit ctx: Context): String =
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,7 @@ object StdNames {
val null_ : N = "null"
val nullExpr: N = "nullExpr"
val ofDim: N = "ofDim"
val on: N = "on"
val opaque: N = "opaque"
val open: N = "open"
val ordinal: N = "ordinal"
Expand Down
57 changes: 45 additions & 12 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ object Parsers {
/* -------------- TOKEN CLASSES ------------------------------------------- */

def isIdent = in.isIdent
def isIdent(name: Name) = in.token == IDENTIFIER && in.name == name
def isIdent(name: Name) = in.isIdent(name)
def isSimpleLiteral = simpleLiteralTokens contains in.token
def isLiteral = literalTokens contains in.token
def isNumericLit = numericLitTokens contains in.token
Expand Down Expand Up @@ -216,10 +216,11 @@ object Parsers {
in.canStartExprTokens.contains(in.token) && !in.isSoftModifierInModifierPosition

def isDefIntro(allowedMods: BitSet, excludedSoftModifiers: Set[TermName] = Set.empty): Boolean =
in.token == AT ||
(defIntroTokens `contains` in.token) ||
(allowedMods `contains` in.token) ||
in.isSoftModifierInModifierPosition && !excludedSoftModifiers.contains(in.name)
in.token == AT
|| defIntroTokens.contains(in.token)
|| allowedMods.contains(in.token)
|| in.isSoftModifierInModifierPosition && !excludedSoftModifiers.contains(in.name)
|| isIdent(nme.extension) && followingIsExtension()

def isStatSep: Boolean = in.isNewLine || in.token == SEMI

Expand Down Expand Up @@ -944,6 +945,13 @@ object Parsers {
lookahead.skipParens()
lookahead.token == COLON || lookahead.token == SUBTYPE

def followingIsExtension() =
val lookahead = in.LookaheadScanner()
lookahead.nextToken()
if lookahead.isIdent && !lookahead.isIdent(nme.on) then
lookahead.nextToken()
lookahead.isIdent(nme.on)

/* --------- OPERAND/OPERATOR STACK --------------------------------------- */

var opStack: List[OpInfo] = Nil
Expand Down Expand Up @@ -3105,7 +3113,7 @@ object Parsers {
* | this ParamClause ParamClauses `=' ConstrExpr
* DefDcl ::= DefSig `:' Type
* DefSig ::= id [DefTypeParamClause] DefParamClauses
* | ExtParamClause [nl] id DefParamClauses
* | ExtParamClause [nl] [‘.’] id DefParamClauses
*/
def defDefOrDcl(start: Offset, mods: Modifiers): Tree = atSpan(start, nameStart) {
def scala2ProcedureSyntax(resultTypeStr: String) = {
Expand Down Expand Up @@ -3134,7 +3142,11 @@ object Parsers {
makeConstructor(Nil, vparamss, rhs).withMods(mods).setComment(in.getDocComment(start))
}
else {
def extParamss() = try paramClause(0, prefix = true) :: Nil finally newLineOpt()
def extParamss() =
try paramClause(0, prefix = true) :: Nil
finally
if in.token == DOT then in.nextToken()
else newLineOpt()
val (leadingTparams, leadingVparamss, flags) =
if in.token == LBRACKET then
(typeParamClause(ParamOwner.Def), extParamss(), Method | Extension)
Expand Down Expand Up @@ -3271,6 +3283,7 @@ object Parsers {
* | [‘case’] ‘object’ ObjectDef
* | ‘enum’ EnumDef
* | ‘given’ GivenDef
* | ‘extension’ ExtensionDef
*/
def tmplDef(start: Int, mods: Modifiers): Tree =
in.token match {
Expand All @@ -3289,8 +3302,11 @@ object Parsers {
case GIVEN =>
givenDef(start, mods, atSpan(in.skipToken()) { Mod.Given() })
case _ =>
syntaxErrorOrIncomplete(ExpectedStartOfTopLevelDefinition())
EmptyTree
if isIdent(nme.extension) && followingIsExtension() then
extensionDef(start, mods)
else
syntaxErrorOrIncomplete(ExpectedStartOfTopLevelDefinition())
EmptyTree
}

/** ClassDef ::= id ClassConstr TemplateOpt
Expand Down Expand Up @@ -3518,6 +3534,23 @@ object Parsers {
finalizeDef(gdef, mods1, start)
}

/** ExtensionDef ::= [id] ‘of’ ExtParamClause {GivenParamClause} ‘with’ ExtMethods
*/
def extensionDef(start: Offset, mods: Modifiers): ModuleDef =
in.nextToken()
val name = if isIdent && !isIdent(nme.on) then ident() else EmptyTermName
if !isIdent(nme.on) then syntaxErrorOrIncomplete("`on` expected")
if isIdent(nme.on) then in.nextToken()
val tparams = typeParamClauseOpt(ParamOwner.Def)
val extParams = paramClause(0, prefix = true)
val givenParamss = paramClauses(givenOnly = true)
possibleTemplateStart()
if !in.isNestedStart then syntaxError("Extension without extension methods")
val templ = templateBodyOpt(makeConstructor(tparams, extParams :: givenParamss), Nil, Nil)
templ.body.foreach(checkExtensionMethod(tparams, _))
val edef = ModuleDef(name, templ)
finalizeDef(edef, addFlag(mods, Given), start)

/* -------- TEMPLATES ------------------------------------------- */

/** SimpleConstrApp ::= AnnotType {ParArgumentExprs}
Expand Down Expand Up @@ -3677,7 +3710,7 @@ object Parsers {
def templateStatSeq(): (ValDef, List[Tree]) = checkNoEscapingPlaceholders {
var self: ValDef = EmptyValDef
val stats = new ListBuffer[Tree]
if (isExprIntro) {
if (isExprIntro && !isDefIntro(modifierTokens)) {
val first = expr1()
if (in.token == ARROW) {
first match {
Expand All @@ -3703,10 +3736,10 @@ object Parsers {
stats ++= importClause(IMPORT, Import)
else if (in.token == EXPORT)
stats ++= importClause(EXPORT, Export.apply)
else if (isExprIntro)
stats += expr1()
else if (isDefIntro(modifierTokensOrCase))
stats +++= defOrDcl(in.offset, defAnnotsMods(modifierTokens))
else if (isExprIntro)
stats += expr1()
else if (!isStatSep) {
exitOnError = mustStartStat
syntaxErrorOrIncomplete("illegal start of definition")
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/parsing/Scanners.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1017,6 +1017,7 @@ object Scanners {

def isNewLine = token == NEWLINE || token == NEWLINES
def isIdent = token == IDENTIFIER || token == BACKQUOTED_IDENT
def isIdent(name: Name) = token == IDENTIFIER && this.name == name

def isNestedStart = token == LBRACE || token == INDENT
def isNestedEnd = token == RBRACE || token == OUTDENT
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1541,7 +1541,7 @@ class Typer extends Namer
var name = tree.name
if (name == nme.WILDCARD && tree.mods.is(Given)) {
val Typed(_, tpt): @unchecked = tree.body
name = desugar.inventGivenName(tpt)
name = desugar.inventGivenOrExtensionName(tpt)
}
if (name == nme.WILDCARD) body1
else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ given extension (s: String) { ... }
```
or
```scala
given listOps: [T](xs: List[T]) extended with { ... }
extension listOps of [T](xs: List[T]) with { ... }

given (s: String) extended with { ... }
```
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/contributing/debugging.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ But you can also do:
assertPositioned(tree.reporting(s"Tree is: $result"))
```

`def (a: A) reporting(f: given WrappedResult[T] => String, p: Printer = Printers.default): A` is defined on all types. The function `f` can be written without the argument since the argument is `given`. The `result` variable is a part of the `WrapperResult` – a tiny framework powering the `reporting` function. Basically, whenever you are using `reporting` on an object `A`, you can use the `result: A` variable from this function and it will be equal to the object you are calling `reporting` on.
`def (a: A).reporting(f: given WrappedResult[T] => String, p: Printer = Printers.default): A` is defined on all types. The function `f` can be written without the argument since the argument is `given`. The `result` variable is a part of the `WrapperResult` – a tiny framework powering the `reporting` function. Basically, whenever you are using `reporting` on an object `A`, you can use the `result: A` variable from this function and it will be equal to the object you are calling `reporting` on.

## Printing out trees after phases
To print out the trees you are compiling after the FrontEnd (scanner, parser, namer, typer) phases:
Expand Down
11 changes: 5 additions & 6 deletions docs/docs/internals/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ yield
### Soft keywords

```
as derives inline opaque open
derives extension inline opaque open
~ * | & + -
```

Expand Down Expand Up @@ -358,7 +358,7 @@ ValDcl ::= ids ‘:’ Type
VarDcl ::= ids ‘:’ Type PatDef(_, ids, tpe, EmptyTree)
DefDcl ::= DefSig ‘:’ Type DefDef(_, name, tparams, vparamss, tpe, EmptyTree)
DefSig ::= id [DefTypeParamClause] DefParamClauses
| ExtParamClause [nl] id DefParamClauses
| ExtParamClause [nl] [‘.’] id DefParamClauses
TypeDcl ::= id [TypeParamClause] SubtypeBounds [‘=’ Type] TypeDefTree(_, name, tparams, bound

Def ::= ‘val’ PatDef
Expand All @@ -378,7 +378,7 @@ TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef
| [‘case’] ‘object’ ObjectDef
| ‘enum’ EnumDef
| ‘given’ GivenDef
| Export
| ‘extension’ ExtensionDef
ClassDef ::= id ClassConstr [Template] ClassDef(mods, name, tparams, templ)
ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses with DefDef(_, <init>, Nil, vparamss, EmptyTree, EmptyTree) as first stat
ConstrMods ::= {Annotation} [AccessModifier]
Expand All @@ -388,11 +388,10 @@ GivenDef ::= [GivenSig (‘:’ | <:)] {FunArgTypes ‘=>’}
AnnotType ‘=’ Expr
| [GivenSig ‘:’] {FunArgTypes ‘=>’}
ConstrApps [[‘with’] TemplateBody]
| [id ‘:’] ExtParamClause {GivenParamClause}
‘extended’ ‘with’ ExtMethods
GivenSig ::= [id] [DefTypeParamClause] {GivenParamClause}
ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’
ExtensionDef ::= [id] ‘of’ ExtParamClause {GivenParamClause} ExtMethods
ExtMethods ::= [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’
ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’
Template ::= InheritClauses [[‘with’] TemplateBody] Template(constr, parents, self, stats)
InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}]
ConstrApps ::= ConstrApp {(‘,’ | ‘with’) ConstrApp}
Expand Down
Loading