Skip to content

Commit 004c4f6

Browse files
committed
Allow exports in extension clauses
This is part of a long term strategy to get deprecate and remove general implicit conversions in Scala. In the thread https://contributors.scala-lang.org/t/can-we-wean-scala-off-implicit-conversions/4388 we identified two areas where implicit conversions or implicit classes were still essential. One was adding methods to new types in bulk. This can be achieved by defining an implicit class that inherits from some other classes that define the methods. Exports in extension methods provide a similar functionality without relying on implicit conversions under the covers.
1 parent d09dd2a commit 004c4f6

File tree

13 files changed

+273
-100
lines changed

13 files changed

+273
-100
lines changed

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

Lines changed: 54 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,16 @@ object desugar {
389389
vparam.withMods(mods & (GivenOrImplicit | Erased | hasDefault) | Param)
390390
}
391391

392+
def mkApply(fn: Tree, paramss: List[ParamClause])(using Context): Tree =
393+
paramss.foldLeft(fn) { (fn, params) => params match
394+
case TypeDefs(params) =>
395+
TypeApply(fn, params.map(refOfDef))
396+
case (vparam: ValDef) :: _ if vparam.mods.is(Given) =>
397+
Apply(fn, params.map(refOfDef)).setApplyKind(ApplyKind.Using)
398+
case _ =>
399+
Apply(fn, params.map(refOfDef))
400+
}
401+
392402
/** The expansion of a class definition. See inline comments for what is involved */
393403
def classDef(cdef: TypeDef)(using Context): Tree = {
394404
val impl @ Template(constr0, _, self, _) = cdef.rhs
@@ -584,22 +594,13 @@ object desugar {
584594
}
585595

586596
// new C[Ts](paramss)
587-
lazy val creatorExpr = {
588-
val vparamss = constrVparamss match {
597+
lazy val creatorExpr =
598+
val vparamss = constrVparamss match
589599
case (vparam :: _) :: _ if vparam.mods.isOneOf(GivenOrImplicit) => // add a leading () to match class parameters
590600
Nil :: constrVparamss
591601
case _ =>
592602
constrVparamss
593-
}
594-
val nu = vparamss.foldLeft(makeNew(classTypeRef)) { (nu, vparams) =>
595-
val app = Apply(nu, vparams.map(refOfDef))
596-
vparams match {
597-
case vparam :: _ if vparam.mods.is(Given) => app.setApplyKind(ApplyKind.Using)
598-
case _ => app
599-
}
600-
}
601-
ensureApplied(nu)
602-
}
603+
ensureApplied(mkApply(makeNew(classTypeRef), vparamss))
603604

604605
val copiedAccessFlags = if migrateTo3 then EmptyFlags else AccessFlags
605606

@@ -895,48 +896,50 @@ object desugar {
895896
}
896897
}
897898

899+
def extMethod(mdef: DefDef, extParamss: List[ParamClause])(using Context): DefDef =
900+
cpy.DefDef(mdef)(
901+
name = normalizeName(mdef, mdef.tpt).asTermName,
902+
paramss =
903+
if mdef.name.isRightAssocOperatorName then
904+
val (typaramss, paramss) = mdef.paramss.span(isTypeParamClause) // first extract type parameters
905+
906+
paramss match
907+
case params :: paramss1 => // `params` must have a single parameter and without `given` flag
908+
909+
def badRightAssoc(problem: String) =
910+
report.error(i"right-associative extension method $problem", mdef.srcPos)
911+
extParamss ++ mdef.paramss
912+
913+
params match
914+
case ValDefs(vparam :: Nil) =>
915+
if !vparam.mods.is(Given) then
916+
// we merge the extension parameters with the method parameters,
917+
// swapping the operator arguments:
918+
// e.g.
919+
// extension [A](using B)(c: C)(using D)
920+
// def %:[E](f: F)(g: G)(using H): Res = ???
921+
// will be encoded as
922+
// def %:[A](using B)[E](f: F)(c: C)(using D)(g: G)(using H): Res = ???
923+
val (leadingUsing, otherExtParamss) = extParamss.span(isUsingOrTypeParamClause)
924+
leadingUsing ::: typaramss ::: params :: otherExtParamss ::: paramss1
925+
else
926+
badRightAssoc("cannot start with using clause")
927+
case _ =>
928+
badRightAssoc("must start with a single parameter")
929+
case _ =>
930+
// no value parameters, so not an infix operator.
931+
extParamss ++ mdef.paramss
932+
else
933+
extParamss ++ mdef.paramss
934+
).withMods(mdef.mods | ExtensionMethod)
935+
898936
/** Transform extension construct to list of extension methods */
899937
def extMethods(ext: ExtMethods)(using Context): Tree = flatTree {
900-
for mdef <- ext.methods yield
901-
defDef(
902-
cpy.DefDef(mdef)(
903-
name = normalizeName(mdef, ext).asTermName,
904-
paramss =
905-
if mdef.name.isRightAssocOperatorName then
906-
val (typaramss, paramss) = mdef.paramss.span(isTypeParamClause) // first extract type parameters
907-
908-
paramss match
909-
case params :: paramss1 => // `params` must have a single parameter and without `given` flag
910-
911-
def badRightAssoc(problem: String) =
912-
report.error(i"right-associative extension method $problem", mdef.srcPos)
913-
ext.paramss ++ mdef.paramss
914-
915-
params match
916-
case ValDefs(vparam :: Nil) =>
917-
if !vparam.mods.is(Given) then
918-
// we merge the extension parameters with the method parameters,
919-
// swapping the operator arguments:
920-
// e.g.
921-
// extension [A](using B)(c: C)(using D)
922-
// def %:[E](f: F)(g: G)(using H): Res = ???
923-
// will be encoded as
924-
// def %:[A](using B)[E](f: F)(c: C)(using D)(g: G)(using H): Res = ???
925-
val (leadingUsing, otherExtParamss) = ext.paramss.span(isUsingOrTypeParamClause)
926-
leadingUsing ::: typaramss ::: params :: otherExtParamss ::: paramss1
927-
else
928-
badRightAssoc("cannot start with using clause")
929-
case _ =>
930-
badRightAssoc("must start with a single parameter")
931-
case _ =>
932-
// no value parameters, so not an infix operator.
933-
ext.paramss ++ mdef.paramss
934-
else
935-
ext.paramss ++ mdef.paramss
936-
).withMods(mdef.mods | ExtensionMethod)
937-
)
938+
ext.methods map {
939+
case exp: Export => exp
940+
case mdef: DefDef => defDef(extMethod(mdef, ext.paramss))
941+
}
938942
}
939-
940943
/** Transforms
941944
*
942945
* <mods> type t >: Low <: Hi

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
118118
case class GenAlias(pat: Tree, expr: Tree)(implicit @constructorOnly src: SourceFile) extends Tree
119119
case class ContextBounds(bounds: TypeBoundsTree, cxBounds: List[Tree])(implicit @constructorOnly src: SourceFile) extends TypTree
120120
case class PatDef(mods: Modifiers, pats: List[Tree], tpt: Tree, rhs: Tree)(implicit @constructorOnly src: SourceFile) extends DefTree
121-
case class ExtMethods(paramss: List[ParamClause], methods: List[DefDef])(implicit @constructorOnly src: SourceFile) extends Tree
121+
case class ExtMethods(paramss: List[ParamClause], methods: List[Tree])(implicit @constructorOnly src: SourceFile) extends Tree
122122
case class MacroTree(expr: Tree)(implicit @constructorOnly src: SourceFile) extends Tree
123123

124124
case class ImportSelector(imported: Ident, renamed: Tree = EmptyTree, bound: Tree = EmptyTree)(implicit @constructorOnly src: SourceFile) extends Tree {
@@ -639,7 +639,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
639639
case tree: PatDef if (mods eq tree.mods) && (pats eq tree.pats) && (tpt eq tree.tpt) && (rhs eq tree.rhs) => tree
640640
case _ => finalize(tree, untpd.PatDef(mods, pats, tpt, rhs)(tree.source))
641641
}
642-
def ExtMethods(tree: Tree)(paramss: List[ParamClause], methods: List[DefDef])(using Context): Tree = tree match
642+
def ExtMethods(tree: Tree)(paramss: List[ParamClause], methods: List[Tree])(using Context): Tree = tree match
643643
case tree: ExtMethods if (paramss eq tree.paramss) && (methods == tree.methods) => tree
644644
case _ => finalize(tree, untpd.ExtMethods(paramss, methods)(tree.source))
645645
def ImportSelector(tree: Tree)(imported: Ident, renamed: Tree, bound: Tree)(using Context): Tree = tree match {

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

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3109,7 +3109,7 @@ object Parsers {
31093109
/** Import ::= `import' ImportExpr {‘,’ ImportExpr}
31103110
* Export ::= `export' ImportExpr {‘,’ ImportExpr}
31113111
*/
3112-
def importClause(leading: Token, mkTree: ImportConstr): List[Tree] = {
3112+
def importOrExportClause(leading: Token, mkTree: ImportConstr): List[Tree] = {
31133113
val offset = accept(leading)
31143114
commaSeparated(importExpr(mkTree)) match {
31153115
case t :: rest =>
@@ -3122,6 +3122,12 @@ object Parsers {
31223122
}
31233123
}
31243124

3125+
def exportClause() =
3126+
importOrExportClause(EXPORT, Export(_,_))
3127+
3128+
def importClause(outermost: Boolean = false) =
3129+
importOrExportClause(IMPORT, mkImport(outermost))
3130+
31253131
/** Create an import node and handle source version imports */
31263132
def mkImport(outermost: Boolean = false): ImportConstr = (tree, selectors) =>
31273133
val imp = Import(tree, selectors)
@@ -3660,8 +3666,10 @@ object Parsers {
36603666
if in.isColon() then
36613667
syntaxError("no `:` expected here")
36623668
in.nextToken()
3663-
val methods =
3664-
if isDefIntro(modifierTokens) then
3669+
val methods: List[Tree] =
3670+
if in.token == EXPORT then
3671+
exportClause()
3672+
else if isDefIntro(modifierTokens) then
36653673
extMethod(nparams) :: Nil
36663674
else
36673675
in.observeIndented()
@@ -3671,12 +3679,13 @@ object Parsers {
36713679
val result = atSpan(start)(ExtMethods(joinParams(tparams, leadParamss.toList), methods))
36723680
val comment = in.getDocComment(start)
36733681
if comment.isDefined then
3674-
for meth <- methods do
3682+
for case meth: DefDef <- methods do
36753683
if !meth.rawComment.isDefined then meth.setComment(comment)
36763684
result
36773685
end extension
36783686

36793687
/** ExtMethod ::= {Annotation [nl]} {Modifier} ‘def’ DefDef
3688+
* | Export
36803689
*/
36813690
def extMethod(numLeadParams: Int): DefDef =
36823691
val start = in.offset
@@ -3686,16 +3695,18 @@ object Parsers {
36863695

36873696
/** ExtMethods ::= ExtMethod | [nl] ‘{’ ExtMethod {semi ExtMethod ‘}’
36883697
*/
3689-
def extMethods(numLeadParams: Int): List[DefDef] = checkNoEscapingPlaceholders {
3690-
val meths = new ListBuffer[DefDef]
3698+
def extMethods(numLeadParams: Int): List[Tree] = checkNoEscapingPlaceholders {
3699+
val meths = new ListBuffer[Tree]
36913700
while
36923701
val start = in.offset
3693-
val mods = defAnnotsMods(modifierTokens)
3694-
in.token != EOF && {
3695-
accept(DEF)
3696-
meths += defDefOrDcl(start, mods, numLeadParams)
3697-
in.token != EOF && statSepOrEnd(meths, what = "extension method")
3698-
}
3702+
if in.token == EXPORT then
3703+
meths ++= exportClause()
3704+
else
3705+
val mods = defAnnotsMods(modifierTokens)
3706+
if in.token != EOF then
3707+
accept(DEF)
3708+
meths += defDefOrDcl(start, mods, numLeadParams)
3709+
in.token != EOF && statSepOrEnd(meths, what = "extension method")
36993710
do ()
37003711
if meths.isEmpty then syntaxErrorOrIncomplete("`def` expected")
37013712
meths.toList
@@ -3843,9 +3854,9 @@ object Parsers {
38433854
else stats += packaging(start)
38443855
}
38453856
else if (in.token == IMPORT)
3846-
stats ++= importClause(IMPORT, mkImport(outermost))
3857+
stats ++= importClause(outermost)
38473858
else if (in.token == EXPORT)
3848-
stats ++= importClause(EXPORT, Export(_,_))
3859+
stats ++= exportClause()
38493860
else if isIdent(nme.extension) && followingIsExtension() then
38503861
stats += extension()
38513862
else if isDefIntro(modifierTokens) then
@@ -3891,9 +3902,9 @@ object Parsers {
38913902
while
38923903
var empty = false
38933904
if (in.token == IMPORT)
3894-
stats ++= importClause(IMPORT, mkImport())
3905+
stats ++= importClause()
38953906
else if (in.token == EXPORT)
3896-
stats ++= importClause(EXPORT, Export(_,_))
3907+
stats ++= exportClause()
38973908
else if isIdent(nme.extension) && followingIsExtension() then
38983909
stats += extension()
38993910
else if (isDefIntro(modifierTokensOrCase))
@@ -3969,7 +3980,7 @@ object Parsers {
39693980
while
39703981
var empty = false
39713982
if (in.token == IMPORT)
3972-
stats ++= importClause(IMPORT, mkImport())
3983+
stats ++= importClause()
39733984
else if (isExprIntro)
39743985
stats += expr(Location.InBlock)
39753986
else if in.token == IMPLICIT && !in.inModifierPosition() then

0 commit comments

Comments
 (0)