Skip to content

Commit 9c987cb

Browse files
Merge pull request #7557 from dotty-staging/given-extends
Change extension method syntax
2 parents 4e57eca + 1dcc26d commit 9c987cb

34 files changed

+345
-220
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -830,14 +830,14 @@ object desugar {
830830
val impl = mdef.impl
831831
val mods = mdef.mods
832832
impl.constr match {
833-
case DefDef(_, tparams, (vparams @ (vparam :: Nil)) :: _, _, _) =>
833+
case DefDef(_, tparams, (vparams @ (vparam :: Nil)) :: givenParamss, _, _) =>
834834
assert(mods.is(Given))
835835
return moduleDef(
836836
cpy.ModuleDef(mdef)(
837837
mdef.name,
838838
cpy.Template(impl)(
839839
constr = emptyConstructor,
840-
body = impl.body.map(makeExtensionDef(_, tparams, vparams)))))
840+
body = impl.body.map(makeExtensionDef(_, tparams, vparams, givenParamss)))))
841841
case _ =>
842842
}
843843

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

895-
def makeExtensionDef(mdef: Tree, tparams: List[TypeDef], leadingParams: List[ValDef])(given ctx: Context): Tree = {
895+
def makeExtensionDef(mdef: Tree, tparams: List[TypeDef], leadingParams: List[ValDef],
896+
givenParamss: List[List[ValDef]])(given ctx: Context): Tree = {
896897
val allowed = "allowed here, since collective parameters are given"
897898
mdef match {
898899
case mdef: DefDef =>
899900
if (mdef.mods.is(Extension)) {
900901
ctx.error(em"No extension method $allowed", mdef.sourcePos)
901902
mdef
902903
}
903-
else cpy.DefDef(mdef)(tparams = tparams ++ mdef.tparams, vparamss = leadingParams :: mdef.vparamss)
904-
.withFlags(Extension)
904+
else cpy.DefDef(mdef)(
905+
tparams = tparams ++ mdef.tparams,
906+
vparamss = leadingParams :: givenParamss ::: mdef.vparamss
907+
).withFlags(Extension)
905908
case mdef: Import =>
906909
mdef
907910
case mdef =>

compiler/src/dotty/tools/dotc/core/Contexts.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -596,7 +596,7 @@ object Contexts {
596596
def setDebug: this.type = setSetting(base.settings.Ydebug, true)
597597
}
598598

599-
given (c: Context)
599+
given ops: (c: Context)
600600
def addNotNullInfo(info: NotNullInfo) =
601601
c.withNotNullInfos(c.notNullInfos.extendWith(info))
602602

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ class Definitions {
9292
* ImplicitFunctionN traits follow this template:
9393
*
9494
* trait ImplicitFunctionN[T0,...,T{N-1}, R] extends Object {
95-
* def apply given ($x0: T0, ..., $x{N_1}: T{N-1}): R
95+
* def apply(given $x0: T0, ..., $x{N_1}: T{N-1}): R
9696
* }
9797
*
9898
* ErasedFunctionN traits follow this template:

compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ Standard-Section: "ASTs" TopLevelStat*
167167
POLYtype Length result_Type NamesTypes -- A polymorphic method type `[NamesTypes]result`, used in refinements
168168
METHODtype Length result_Type NamesTypes -- A method type `(NamesTypes)result`, needed for refinements
169169
ERASEDMETHODtype Length result_Type NamesTypes -- A method type `erased (NamesTypes)result`, needed for refinements
170-
GIVENMETHODtype Length result_Type NamesTypes -- A method type `given (NamesTypes)result`, needed for refinements
170+
GIVENMETHODtype Length result_Type NamesTypes -- A method type `(given NamesTypes)result`, needed for refinements
171171
ERASEDGIVENMETHODtype Length result_Type NamesTypes -- A method type `given(erased NamesTypes)result`, needed for refinements
172172
IMPLICITMETHODtype Length result_Type NamesTypes -- A method type `(implicit NamesTypes)result`, needed for refinements
173173
// TODO: remove ERASEDIMPLICITMETHODtype

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

Lines changed: 67 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2733,7 +2733,7 @@ object Parsers {
27332733
* DefTypeParamClause::= ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’
27342734
* DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeParamBounds
27352735
*
2736-
* TypTypeParamCaluse::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’
2736+
* TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’
27372737
* TypTypeParam ::= {Annotation} id [HkTypePamClause] TypeBounds
27382738
*
27392739
* HkTypeParamClause ::= ‘[’ HkTypeParam {‘,’ HkTypeParam} ‘]’
@@ -2799,6 +2799,7 @@ object Parsers {
27992799
ofClass: Boolean = false, // owner is a class
28002800
ofCaseClass: Boolean = false, // owner is a case class
28012801
prefix: Boolean = false, // clause precedes name of an extension method
2802+
givenOnly: Boolean = false, // only given parameters allowed
28022803
firstClause: Boolean = false // clause is the first in regular list of clauses
28032804
): List[ValDef] = {
28042805
var impliedMods: Modifiers = EmptyModifiers
@@ -2870,6 +2871,8 @@ object Parsers {
28702871
if !impliedModOpt(IMPLICIT, () => Mod.Implicit()) then
28712872
impliedModOpt(GIVEN, () => Mod.Given())
28722873
impliedModOpt(ERASED, () => Mod.Erased())
2874+
if givenOnly && !impliedMods.is(Given) then
2875+
syntaxError(ExpectedTokenButFound(GIVEN, in.token))
28732876
val isParams =
28742877
!impliedMods.is(Given)
28752878
|| startParamTokens.contains(in.token)
@@ -2890,7 +2893,7 @@ object Parsers {
28902893
*/
28912894
def paramClauses(ofClass: Boolean = false,
28922895
ofCaseClass: Boolean = false,
2893-
ofInstance: Boolean = false): List[List[ValDef]] =
2896+
givenOnly: Boolean = false): List[List[ValDef]] =
28942897

28952898
def recur(firstClause: Boolean, nparams: Int): List[List[ValDef]] =
28962899
newLineOptWhenFollowedBy(LPAREN)
@@ -2900,6 +2903,7 @@ object Parsers {
29002903
nparams,
29012904
ofClass = ofClass,
29022905
ofCaseClass = ofCaseClass,
2906+
givenOnly = givenOnly,
29032907
firstClause = firstClause)
29042908
val lastClause = params.nonEmpty && params.head.mods.flags.is(Implicit)
29052909
params :: (
@@ -3394,58 +3398,74 @@ object Parsers {
33943398

33953399
/** GivenDef ::= [GivenSig (‘:’ | <:)] Type ‘=’ Expr
33963400
* | [GivenSig ‘:’] [ConstrApp {‘,’ ConstrApp }] [TemplateBody]
3397-
* | [id ‘:’] ExtParamClause ExtMethods
3401+
* | [id ‘:’] ‘extension’ ExtParamClause {GivenParamClause} ExtMethods
33983402
* GivenSig ::= [id] [DefTypeParamClause] {GivenParamClause}
33993403
* ExtParamClause ::= [DefTypeParamClause] DefParamClause {GivenParamClause}
34003404
* ExtMethods ::= [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’
34013405
*/
34023406
def givenDef(start: Offset, mods: Modifiers, instanceMod: Mod) = atSpan(start, nameStart) {
34033407
var mods1 = addMod(mods, instanceMod)
34043408
val hasGivenSig = followingIsGivenSig()
3405-
val name = if isIdent && hasGivenSig then ident() else EmptyTermName
3406-
indentRegion(name) {
3407-
var tparams: List[TypeDef] = Nil
3408-
var vparamss: List[List[ValDef]] = Nil
3409-
var hasExtensionParams = false
3410-
3411-
def parseParams(isExtension: Boolean): Unit =
3412-
if isExtension && (in.token == LBRACKET || in.token == LPAREN) then
3413-
hasExtensionParams = true
3414-
if tparams.nonEmpty || vparamss.nonEmpty then
3415-
syntaxError(i"cannot have parameters before and after `:` in extension")
3416-
if in.token == LBRACKET then
3417-
tparams = typeParamClause(ParamOwner.Def)
3418-
if in.token == LPAREN && followingIsParamOrGivenType() then
3419-
val paramsStart = in.offset
3420-
vparamss = paramClauses(ofInstance = !isExtension)
3421-
if isExtension then
3422-
checkExtensionParams(paramsStart, vparamss)
3423-
3424-
parseParams(isExtension = !hasGivenSig)
3425-
val parents =
3426-
if in.token == COLON then
3427-
in.nextToken()
3428-
if in.token == LBRACE
3429-
|| in.token == WITH
3430-
|| in.token == LBRACKET
3431-
|| in.token == LPAREN && followingIsParamOrGivenType()
3432-
then
3433-
parseParams(isExtension = true)
3434-
Nil
3435-
else
3436-
tokenSeparated(COMMA, constrApp)
3437-
else if in.token == SUBTYPE then
3438-
if !mods.is(Inline) then
3439-
syntaxError("`<:' is only allowed for given with `inline' modifier")
3440-
in.nextToken()
3441-
TypeBoundsTree(EmptyTree, toplevelTyp()) :: Nil
3442-
else if name.isEmpty
3443-
&& in.token != LBRACE && in.token != WITH
3444-
&& !hasExtensionParams
3445-
then tokenSeparated(COMMA, constrApp)
3446-
else Nil
3409+
val (name, isExtension) =
3410+
if isIdent && hasGivenSig then
3411+
(ident(), in.token == COLON && in.lookaheadIn(nme.extension))
3412+
else
3413+
(EmptyTermName, isIdent(nme.extension))
3414+
3415+
val gdef = indentRegion(name) {
3416+
if isExtension then
3417+
if (in.token == COLON) in.nextToken()
3418+
assert(ident() == nme.extension)
3419+
val tparams = typeParamClauseOpt(ParamOwner.Def)
3420+
val extParams = paramClause(0, prefix = true)
3421+
val givenParamss = paramClauses(givenOnly = true)
3422+
possibleTemplateStart()
3423+
val templ = templateBodyOpt(
3424+
makeConstructor(tparams, extParams :: givenParamss), Nil, Nil)
3425+
templ.body.foreach(checkExtensionMethod)
3426+
ModuleDef(name, templ)
3427+
else
3428+
var tparams: List[TypeDef] = Nil
3429+
var vparamss: List[List[ValDef]] = Nil
3430+
var hasExtensionParams = false
3431+
3432+
def parseParams(isExtension: Boolean): Unit =
3433+
if isExtension && (in.token == LBRACKET || in.token == LPAREN) then
3434+
hasExtensionParams = true
3435+
if tparams.nonEmpty || vparamss.nonEmpty then
3436+
syntaxError(i"cannot have parameters before and after `:` in extension")
3437+
if in.token == LBRACKET then
3438+
tparams = typeParamClause(ParamOwner.Def)
3439+
if in.token == LPAREN && followingIsParamOrGivenType() then
3440+
val paramsStart = in.offset
3441+
vparamss = paramClauses(givenOnly = !isExtension)
3442+
if isExtension then
3443+
checkExtensionParams(paramsStart, vparamss)
3444+
3445+
parseParams(isExtension = false)
3446+
val parents =
3447+
if in.token == COLON then
3448+
in.nextToken()
3449+
if in.token == LBRACE
3450+
|| in.token == WITH
3451+
|| in.token == LBRACKET
3452+
|| in.token == LPAREN && followingIsParamOrGivenType()
3453+
then
3454+
parseParams(isExtension = true)
3455+
Nil
3456+
else
3457+
tokenSeparated(COMMA, constrApp)
3458+
else if in.token == SUBTYPE then
3459+
if !mods.is(Inline) then
3460+
syntaxError("`<:' is only allowed for given with `inline' modifier")
3461+
in.nextToken()
3462+
TypeBoundsTree(EmptyTree, toplevelTyp()) :: Nil
3463+
else if name.isEmpty
3464+
&& in.token != LBRACE && in.token != WITH
3465+
&& !hasExtensionParams
3466+
then tokenSeparated(COMMA, constrApp)
3467+
else Nil
34473468

3448-
val gdef =
34493469
if in.token == EQUALS && parents.length == 1 && parents.head.isType then
34503470
in.nextToken()
34513471
mods1 |= Final
@@ -3467,9 +3487,8 @@ object Parsers {
34673487
ModuleDef(name, templ)
34683488
else if tparams.isEmpty && vparamss.isEmpty then ModuleDef(name, templ)
34693489
else TypeDef(name.toTypeName, templ)
3470-
3471-
finalizeDef(gdef, mods1, start)
34723490
}
3491+
finalizeDef(gdef, mods1, start)
34733492
}
34743493

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

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -895,14 +895,15 @@ object Scanners {
895895
nextToken()
896896

897897
/** Is the token following the current one in `tokens`? */
898-
def lookaheadIn(tokens: BitSet): Boolean = {
898+
def lookaheadIn(follow: BitSet | TermName): Boolean =
899899
val lookahead = LookaheadScanner()
900900
while
901901
lookahead.nextToken()
902902
lookahead.isNewLine
903903
do ()
904-
tokens.contains(lookahead.token)
905-
}
904+
follow match
905+
case tokens: BitSet => tokens.contains(lookahead.token)
906+
case name: TermName => lookahead.token == IDENTIFIER && lookahead.name == name
906907

907908
/** Is the current token in a position where a modifier is allowed? */
908909
def inModifierPosition(): Boolean = {

compiler/src/dotty/tools/dotc/typer/Implicits.scala

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,16 +1460,29 @@ trait Implicits { self: Typer =>
14601460
* - If alt2 is preferred over alt1, pick alt2, otherwise return an
14611461
* ambiguous implicits error.
14621462
*/
1463-
def disambiguate(alt1: SearchResult, alt2: SearchSuccess) = alt1 match {
1463+
def disambiguate(alt1: SearchResult, alt2: SearchSuccess) = alt1 match
14641464
case alt1: SearchSuccess =>
1465-
val diff = compareCandidate(alt1, alt2.ref, alt2.level)
1465+
var diff = compareCandidate(alt1, alt2.ref, alt2.level)
14661466
assert(diff <= 0) // diff > 0 candidates should already have been eliminated in `rank`
1467-
if (diff < 0) alt2
1468-
else
1469-
// numericValueTypeBreak(alt1, alt2) recoverWith
1470-
SearchFailure(new AmbiguousImplicits(alt1, alt2, pt, argument))
1467+
1468+
if diff == 0 then
1469+
// Fall back: if both results are extension method applications,
1470+
// compare the extension methods instead of their wrappers.
1471+
object extMethodApply with
1472+
def unapply(t: Tree): Option[Type] = t match
1473+
case t: Applications.ExtMethodApply => Some(methPart(stripApply(t.app)).tpe)
1474+
case _ => None
1475+
end extMethodApply
1476+
1477+
(alt1.tree, alt2.tree) match
1478+
case (extMethodApply(ref1: TermRef), extMethodApply(ref2: TermRef)) =>
1479+
diff = compare(ref1, ref2)
1480+
case _ =>
1481+
1482+
if diff < 0 then alt2
1483+
else if diff > 0 then alt1
1484+
else SearchFailure(new AmbiguousImplicits(alt1, alt2, pt, argument))
14711485
case _: SearchFailure => alt2
1472-
}
14731486

14741487
/** Faced with an ambiguous implicits failure `fail`, try to find another
14751488
* alternative among `pending` that is strictly better than both ambiguous

compiler/src/dotty/tools/dotc/typer/Nullables.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ object Nullables with
137137
// TODO: Add constant pattern if the constant type is not nullable
138138
case _ => false
139139

140-
given (infos: List[NotNullInfo])
140+
given notNullInfoOps: (infos: List[NotNullInfo])
141141

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

164-
given (tree: Tree)
164+
given treeOps: (tree: Tree)
165165

166166
/* The `tree` with added nullability attachment */
167167
def withNotNullInfo(info: NotNullInfo): tree.type =
@@ -251,7 +251,7 @@ object Nullables with
251251
tree.computeNullable()
252252
}.traverse(tree)
253253

254-
given (tree: Assign)
254+
given assignOps: (tree: Assign)
255255
def computeAssignNullable()(given Context): tree.type = tree.lhs match
256256
case TrackedRef(ref) =>
257257
tree.withNotNullInfo(NotNullInfo(Set(), Set(ref))) // TODO: refine with nullability type info

docs/docs/internals/syntax.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -388,9 +388,10 @@ ObjectDef ::= id [Template]
388388
EnumDef ::= id ClassConstr InheritClauses EnumBody EnumDef(mods, name, tparams, template)
389389
GivenDef ::= [GivenSig (‘:’ | <:)] Type ‘=’ Expr
390390
| [GivenSig ‘:’] [ConstrApp {‘,’ ConstrApp }] [TemplateBody]
391-
| [GivenSig ‘:’] ExtParamClause ExtMethods
391+
| [[id ‘:’] ‘extension’ ExtParamClause {GivenParamClause}
392+
ExtMethods
392393
GivenSig ::= [id] [DefTypeParamClause] {GivenParamClause}
393-
ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’ {GivenParamClause}
394+
ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’
394395
ExtMethods ::= [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’
395396
Template ::= InheritClauses [TemplateBody] Template(constr, parents, self, stats)
396397
InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}]

docs/docs/reference/changed-features/numeric-literals.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ no code that can be executed at runtime. That's why we define an intermediary cl
201201
method in the `FromDigits` given instance. That method is defined in terms of a macro
202202
implementation method `fromDigitsImpl`. Here is its definition:
203203
```scala
204-
private def fromDigitsImpl(digits: Expr[String]) given (ctx: QuoteContext): Expr[BigFloat] =
204+
private def fromDigitsImpl(digits: Expr[String])(given ctx: QuoteContext): Expr[BigFloat] =
205205
digits match {
206206
case Const(ds) =>
207207
try {

0 commit comments

Comments
 (0)