Skip to content

Change extension method syntax #7557

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 7 commits into from
Nov 18, 2019
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
13 changes: 8 additions & 5 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -830,14 +830,14 @@ object desugar {
val impl = mdef.impl
val mods = mdef.mods
impl.constr match {
case DefDef(_, tparams, (vparams @ (vparam :: Nil)) :: _, _, _) =>
case DefDef(_, tparams, (vparams @ (vparam :: Nil)) :: givenParamss, _, _) =>
assert(mods.is(Given))
return moduleDef(
cpy.ModuleDef(mdef)(
mdef.name,
cpy.Template(impl)(
constr = emptyConstructor,
body = impl.body.map(makeExtensionDef(_, tparams, vparams)))))
body = impl.body.map(makeExtensionDef(_, tparams, vparams, givenParamss)))))
case _ =>
}

Expand Down Expand Up @@ -892,16 +892,19 @@ object desugar {
* If the given member `mdef` is not of this form, flag it as an error.
*/

def makeExtensionDef(mdef: Tree, tparams: List[TypeDef], leadingParams: List[ValDef])(given ctx: Context): Tree = {
def makeExtensionDef(mdef: Tree, tparams: List[TypeDef], leadingParams: List[ValDef],
givenParamss: List[List[ValDef]])(given ctx: Context): Tree = {
val allowed = "allowed here, since collective parameters are given"
mdef match {
case mdef: DefDef =>
if (mdef.mods.is(Extension)) {
ctx.error(em"No extension method $allowed", mdef.sourcePos)
mdef
}
else cpy.DefDef(mdef)(tparams = tparams ++ mdef.tparams, vparamss = leadingParams :: mdef.vparamss)
.withFlags(Extension)
else cpy.DefDef(mdef)(
tparams = tparams ++ mdef.tparams,
vparamss = leadingParams :: givenParamss ::: mdef.vparamss
).withFlags(Extension)
case mdef: Import =>
mdef
case mdef =>
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,7 @@ object Contexts {
def setDebug: this.type = setSetting(base.settings.Ydebug, true)
}

given (c: Context)
given ops: (c: Context)
def addNotNullInfo(info: NotNullInfo) =
c.withNotNullInfos(c.notNullInfos.extendWith(info))

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class Definitions {
* ImplicitFunctionN traits follow this template:
*
* trait ImplicitFunctionN[T0,...,T{N-1}, R] extends Object {
* def apply given ($x0: T0, ..., $x{N_1}: T{N-1}): R
* def apply(given $x0: T0, ..., $x{N_1}: T{N-1}): R
* }
*
* ErasedFunctionN traits follow this template:
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ Standard-Section: "ASTs" TopLevelStat*
POLYtype Length result_Type NamesTypes -- A polymorphic method type `[NamesTypes]result`, used in refinements
METHODtype Length result_Type NamesTypes -- A method type `(NamesTypes)result`, needed for refinements
ERASEDMETHODtype Length result_Type NamesTypes -- A method type `erased (NamesTypes)result`, needed for refinements
GIVENMETHODtype Length result_Type NamesTypes -- A method type `given (NamesTypes)result`, needed for refinements
GIVENMETHODtype Length result_Type NamesTypes -- A method type `(given NamesTypes)result`, needed for refinements
ERASEDGIVENMETHODtype Length result_Type NamesTypes -- A method type `given(erased NamesTypes)result`, needed for refinements
IMPLICITMETHODtype Length result_Type NamesTypes -- A method type `(implicit NamesTypes)result`, needed for refinements
// TODO: remove ERASEDIMPLICITMETHODtype
Expand Down
115 changes: 67 additions & 48 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2733,7 +2733,7 @@ object Parsers {
* DefTypeParamClause::= ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’
* DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeParamBounds
*
* TypTypeParamCaluse::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’
* TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’
* TypTypeParam ::= {Annotation} id [HkTypePamClause] TypeBounds
*
* HkTypeParamClause ::= ‘[’ HkTypeParam {‘,’ HkTypeParam} ‘]’
Expand Down Expand Up @@ -2799,6 +2799,7 @@ object Parsers {
ofClass: Boolean = false, // owner is a class
ofCaseClass: Boolean = false, // owner is a case class
prefix: Boolean = false, // clause precedes name of an extension method
givenOnly: Boolean = false, // only given parameters allowed
firstClause: Boolean = false // clause is the first in regular list of clauses
): List[ValDef] = {
var impliedMods: Modifiers = EmptyModifiers
Expand Down Expand Up @@ -2870,6 +2871,8 @@ object Parsers {
if !impliedModOpt(IMPLICIT, () => Mod.Implicit()) then
impliedModOpt(GIVEN, () => Mod.Given())
impliedModOpt(ERASED, () => Mod.Erased())
if givenOnly && !impliedMods.is(Given) then
syntaxError(ExpectedTokenButFound(GIVEN, in.token))
val isParams =
!impliedMods.is(Given)
|| startParamTokens.contains(in.token)
Expand All @@ -2890,7 +2893,7 @@ object Parsers {
*/
def paramClauses(ofClass: Boolean = false,
ofCaseClass: Boolean = false,
ofInstance: Boolean = false): List[List[ValDef]] =
givenOnly: Boolean = false): List[List[ValDef]] =

def recur(firstClause: Boolean, nparams: Int): List[List[ValDef]] =
newLineOptWhenFollowedBy(LPAREN)
Expand All @@ -2900,6 +2903,7 @@ object Parsers {
nparams,
ofClass = ofClass,
ofCaseClass = ofCaseClass,
givenOnly = givenOnly,
firstClause = firstClause)
val lastClause = params.nonEmpty && params.head.mods.flags.is(Implicit)
params :: (
Expand Down Expand Up @@ -3394,58 +3398,74 @@ object Parsers {

/** GivenDef ::= [GivenSig (‘:’ | <:)] Type ‘=’ Expr
* | [GivenSig ‘:’] [ConstrApp {‘,’ ConstrApp }] [TemplateBody]
* | [id ‘:’] ExtParamClause ExtMethods
* | [id ‘:’] ‘extension’ ExtParamClause {GivenParamClause} ExtMethods
* GivenSig ::= [id] [DefTypeParamClause] {GivenParamClause}
* ExtParamClause ::= [DefTypeParamClause] DefParamClause {GivenParamClause}
* ExtMethods ::= [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’
*/
def givenDef(start: Offset, mods: Modifiers, instanceMod: Mod) = atSpan(start, nameStart) {
var mods1 = addMod(mods, instanceMod)
val hasGivenSig = followingIsGivenSig()
val name = if isIdent && hasGivenSig then ident() else EmptyTermName
indentRegion(name) {
var tparams: List[TypeDef] = Nil
var vparamss: List[List[ValDef]] = Nil
var hasExtensionParams = false

def parseParams(isExtension: Boolean): Unit =
if isExtension && (in.token == LBRACKET || in.token == LPAREN) then
hasExtensionParams = true
if tparams.nonEmpty || vparamss.nonEmpty then
syntaxError(i"cannot have parameters before and after `:` in extension")
if in.token == LBRACKET then
tparams = typeParamClause(ParamOwner.Def)
if in.token == LPAREN && followingIsParamOrGivenType() then
val paramsStart = in.offset
vparamss = paramClauses(ofInstance = !isExtension)
if isExtension then
checkExtensionParams(paramsStart, vparamss)

parseParams(isExtension = !hasGivenSig)
val parents =
if in.token == COLON then
in.nextToken()
if in.token == LBRACE
|| in.token == WITH
|| in.token == LBRACKET
|| in.token == LPAREN && followingIsParamOrGivenType()
then
parseParams(isExtension = true)
Nil
else
tokenSeparated(COMMA, constrApp)
else if in.token == SUBTYPE then
if !mods.is(Inline) then
syntaxError("`<:' is only allowed for given with `inline' modifier")
in.nextToken()
TypeBoundsTree(EmptyTree, toplevelTyp()) :: Nil
else if name.isEmpty
&& in.token != LBRACE && in.token != WITH
&& !hasExtensionParams
then tokenSeparated(COMMA, constrApp)
else Nil
val (name, isExtension) =
if isIdent && hasGivenSig then
(ident(), in.token == COLON && in.lookaheadIn(nme.extension))
else
(EmptyTermName, isIdent(nme.extension))

val gdef = indentRegion(name) {
if isExtension then
if (in.token == COLON) in.nextToken()
assert(ident() == nme.extension)
val tparams = typeParamClauseOpt(ParamOwner.Def)
val extParams = paramClause(0, prefix = true)
val givenParamss = paramClauses(givenOnly = true)
possibleTemplateStart()
val templ = templateBodyOpt(
makeConstructor(tparams, extParams :: givenParamss), Nil, Nil)
templ.body.foreach(checkExtensionMethod)
ModuleDef(name, templ)
else
var tparams: List[TypeDef] = Nil
var vparamss: List[List[ValDef]] = Nil
var hasExtensionParams = false

def parseParams(isExtension: Boolean): Unit =
if isExtension && (in.token == LBRACKET || in.token == LPAREN) then
hasExtensionParams = true
if tparams.nonEmpty || vparamss.nonEmpty then
syntaxError(i"cannot have parameters before and after `:` in extension")
if in.token == LBRACKET then
tparams = typeParamClause(ParamOwner.Def)
if in.token == LPAREN && followingIsParamOrGivenType() then
val paramsStart = in.offset
vparamss = paramClauses(givenOnly = !isExtension)
if isExtension then
checkExtensionParams(paramsStart, vparamss)

parseParams(isExtension = false)
val parents =
if in.token == COLON then
in.nextToken()
if in.token == LBRACE
|| in.token == WITH
|| in.token == LBRACKET
|| in.token == LPAREN && followingIsParamOrGivenType()
then
parseParams(isExtension = true)
Nil
else
tokenSeparated(COMMA, constrApp)
else if in.token == SUBTYPE then
if !mods.is(Inline) then
syntaxError("`<:' is only allowed for given with `inline' modifier")
in.nextToken()
TypeBoundsTree(EmptyTree, toplevelTyp()) :: Nil
else if name.isEmpty
&& in.token != LBRACE && in.token != WITH
&& !hasExtensionParams
then tokenSeparated(COMMA, constrApp)
else Nil

val gdef =
if in.token == EQUALS && parents.length == 1 && parents.head.isType then
in.nextToken()
mods1 |= Final
Expand All @@ -3467,9 +3487,8 @@ object Parsers {
ModuleDef(name, templ)
else if tparams.isEmpty && vparamss.isEmpty then ModuleDef(name, templ)
else TypeDef(name.toTypeName, templ)

finalizeDef(gdef, mods1, start)
}
finalizeDef(gdef, mods1, start)
}

/* -------- TEMPLATES ------------------------------------------- */
Expand Down
7 changes: 4 additions & 3 deletions compiler/src/dotty/tools/dotc/parsing/Scanners.scala
Original file line number Diff line number Diff line change
Expand Up @@ -895,14 +895,15 @@ object Scanners {
nextToken()

/** Is the token following the current one in `tokens`? */
def lookaheadIn(tokens: BitSet): Boolean = {
def lookaheadIn(follow: BitSet | TermName): Boolean =
val lookahead = LookaheadScanner()
while
lookahead.nextToken()
lookahead.isNewLine
do ()
tokens.contains(lookahead.token)
}
follow match
case tokens: BitSet => tokens.contains(lookahead.token)
case name: TermName => lookahead.token == IDENTIFIER && lookahead.name == name

/** Is the current token in a position where a modifier is allowed? */
def inModifierPosition(): Boolean = {
Expand Down
27 changes: 20 additions & 7 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1455,16 +1455,29 @@ trait Implicits { self: Typer =>
* - If alt2 is preferred over alt1, pick alt2, otherwise return an
* ambiguous implicits error.
*/
def disambiguate(alt1: SearchResult, alt2: SearchSuccess) = alt1 match {
def disambiguate(alt1: SearchResult, alt2: SearchSuccess) = alt1 match
case alt1: SearchSuccess =>
val diff = compareCandidate(alt1, alt2.ref, alt2.level)
var diff = compareCandidate(alt1, alt2.ref, alt2.level)
assert(diff <= 0) // diff > 0 candidates should already have been eliminated in `rank`
if (diff < 0) alt2
else
// numericValueTypeBreak(alt1, alt2) recoverWith
SearchFailure(new AmbiguousImplicits(alt1, alt2, pt, argument))

if diff == 0 then
// Fall back: if both results are extension method applications,
// compare the extension methods instead of their wrappers.
object extMethodApply with
def unapply(t: Tree): Option[Type] = t match
case t: Applications.ExtMethodApply => Some(methPart(stripApply(t.app)).tpe)
case _ => None
end extMethodApply

(alt1.tree, alt2.tree) match
case (extMethodApply(ref1: TermRef), extMethodApply(ref2: TermRef)) =>
diff = compare(ref1, ref2)
case _ =>

if diff < 0 then alt2
else if diff > 0 then alt1
else SearchFailure(new AmbiguousImplicits(alt1, alt2, pt, argument))
case _: SearchFailure => alt2
}

/** Faced with an ambiguous implicits failure `fail`, try to find another
* alternative among `pending` that is strictly better than both ambiguous
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Nullables.scala
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ object Nullables with
// TODO: Add constant pattern if the constant type is not nullable
case _ => false

given (infos: List[NotNullInfo])
given notNullInfoOps: (infos: List[NotNullInfo])

/** Do the current not-null infos imply that `ref` is not null?
* Not-null infos are as a history where earlier assertions and retractions replace
Expand All @@ -161,7 +161,7 @@ object Nullables with
then infos
else info :: infos

given (tree: Tree)
given treeOps: (tree: Tree)

/* The `tree` with added nullability attachment */
def withNotNullInfo(info: NotNullInfo): tree.type =
Expand Down Expand Up @@ -251,7 +251,7 @@ object Nullables with
tree.computeNullable()
}.traverse(tree)

given (tree: Assign)
given assignOps: (tree: Assign)
def computeAssignNullable()(given Context): tree.type = tree.lhs match
case TrackedRef(ref) =>
tree.withNotNullInfo(NotNullInfo(Set(), Set(ref))) // TODO: refine with nullability type info
Expand Down
5 changes: 3 additions & 2 deletions docs/docs/internals/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,9 +388,10 @@ ObjectDef ::= id [Template]
EnumDef ::= id ClassConstr InheritClauses EnumBody EnumDef(mods, name, tparams, template)
GivenDef ::= [GivenSig (‘:’ | <:)] Type ‘=’ Expr
| [GivenSig ‘:’] [ConstrApp {‘,’ ConstrApp }] [TemplateBody]
| [GivenSig ‘:’] ExtParamClause ExtMethods
| [[id ‘:’] ‘extension’ ExtParamClause {GivenParamClause}
ExtMethods
GivenSig ::= [id] [DefTypeParamClause] {GivenParamClause}
ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’ {GivenParamClause}
ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’
ExtMethods ::= [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’
Template ::= InheritClauses [TemplateBody] Template(constr, parents, self, stats)
InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}]
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/reference/changed-features/numeric-literals.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ no code that can be executed at runtime. That's why we define an intermediary cl
method in the `FromDigits` given instance. That method is defined in terms of a macro
implementation method `fromDigitsImpl`. Here is its definition:
```scala
private def fromDigitsImpl(digits: Expr[String]) given (ctx: QuoteContext): Expr[BigFloat] =
private def fromDigitsImpl(digits: Expr[String])(given ctx: QuoteContext): Expr[BigFloat] =
digits match {
case Const(ds) =>
try {
Expand Down
Loading