Skip to content
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

Unified extension methods #9255

Merged
merged 38 commits into from
Jul 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
3cc3745
Unified extension methods
odersky Jun 28, 2020
d88a796
Change parser to accept new extension syntax
odersky Jun 28, 2020
f0c2e19
Doc fixes
odersky Jun 28, 2020
f87ec07
Desugaring of extensions
odersky Jun 29, 2020
e5ca6d5
Doc fixes
odersky Jun 29, 2020
02de5dc
Prefix extension methods with `extension_`.
odersky Jun 29, 2020
70d5ec8
Print extension methods with new syntax
odersky Jun 30, 2020
28ef7b6
Update semanticDB expect files
odersky Jun 30, 2020
74865c3
Disallow normal method names starting with `extension_`
odersky Jun 30, 2020
4c50dbd
Adapt to new extension_ syntax in CB
odersky Jun 30, 2020
5b1a2f7
Look for extension methods directly in implicit scope
odersky Jun 30, 2020
71a853a
Fixes to parsing and desugaring of extension methods
odersky Jun 30, 2020
fb93f46
Fix: wrap extensions in toplevel package objects
odersky Jun 30, 2020
1ad5ee9
Make type parameters after extension method `def` illegal
odersky Jun 30, 2020
6ff0ac8
Update tests and docs to new extension syntax
odersky Jun 30, 2020
c148218
Update semanticDB expect files
odersky Jun 30, 2020
330c32f
Fix test
odersky Jun 30, 2020
b63da92
Disable check file for missing-implicit
odersky Jun 30, 2020
3e89b56
Allow `end extension` as an endmarker
odersky Jul 1, 2020
a7213cc
Fix extension method search in implicit scope
odersky Jul 1, 2020
00cc25d
Fix syntax error messages for extensions
odersky Jul 1, 2020
5cdb3da
Drop collective extensions from tests and docs
odersky Jul 1, 2020
ce6d9ca
Drop outdated repl test
odersky Jul 1, 2020
40667a6
Update semanticDB expect files
odersky Jul 1, 2020
3188b43
Avoid double-counting of extension methods
odersky Jul 1, 2020
2cd1092
Handle missing case for extension/conversion ambiguity
odersky Jul 1, 2020
8529885
Check no use of extension_ in definitions at Desugar
odersky Jul 1, 2020
c1b5150
Avoid printing same extension method attempt multiple times
odersky Jul 1, 2020
e33d36f
Drop check file
odersky Jul 1, 2020
026b58a
Fix #8311: Propagate more information about extension method arguments
odersky Jul 1, 2020
01ec288
Refine criterion for deepening after ambiguities
odersky Jul 1, 2020
17a8026
Add clarification to doc page
odersky Jul 2, 2020
7de235d
Add test
odersky Jul 3, 2020
4aae35c
Fix printing right-associative extension methods
odersky Jul 3, 2020
c8d7be1
Address review comments
odersky Jul 3, 2020
a128dab
Drop `:` for collective extensions
odersky Jul 4, 2020
b179a8e
Change indentation.md to reflect no colon in extensions
odersky Jul 4, 2020
e160140
Allow optional `:` in extension
odersky Jul 5, 2020
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
2 changes: 1 addition & 1 deletion community-build/community-projects/utest
36 changes: 31 additions & 5 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ object desugar {
val copiedAccessFlags = if migrateTo3 then EmptyFlags else AccessFlags

// Methods to add to a case class C[..](p1: T1, ..., pN: Tn)(moreParams)
// def _1: T1 = this.p1
// def _1: T1 = this.p1
// ...
// def _N: TN = this.pN (unless already given as valdef or parameterless defdef)
// def copy(p1: T1 = p1: @uncheckedVariance, ...,
Expand All @@ -572,7 +572,7 @@ object desugar {
val caseClassMeths = {
def syntheticProperty(name: TermName, tpt: Tree, rhs: Tree) =
DefDef(name, Nil, Nil, tpt, rhs).withMods(synthetic)

def productElemMeths =
val caseParams = derivedVparamss.head.toArray
val selectorNamesInBody = normalizedBody.collect {
Expand Down Expand Up @@ -850,6 +850,7 @@ object desugar {
* where every definition in `body` is expanded to an extension method
* taking type parameters `tparams` and a leading paramter `(x: T)`.
* See: collectiveExtensionBody
* TODO: drop this part
odersky marked this conversation as resolved.
Show resolved Hide resolved
*/
def moduleDef(mdef: ModuleDef)(implicit ctx: Context): Tree = {
val impl = mdef.impl
Expand Down Expand Up @@ -906,6 +907,25 @@ object desugar {
}
}

/** Transform extension construct to list of extension methods */
def extMethods(ext: ExtMethods)(using Context): Tree = flatTree {
for mdef <- ext.methods yield
if mdef.tparams.nonEmpty then
ctx.error("extension method cannot have type parameters here, all type parameters go after `extension`",
mdef.tparams.head.sourcePos)
defDef(
cpy.DefDef(mdef)(
name = mdef.name.toExtensionName,
tparams = ext.tparams ++ mdef.tparams,
vparamss = mdef.vparamss match
case vparams1 :: vparamss1 if !isLeftAssoc(mdef.name) =>
vparams1 :: ext.vparamss ::: vparamss1
case _ =>
ext.vparamss ++ mdef.vparamss
).withMods(mdef.mods | Extension)
)
}

/** Transform the statements of a collective extension
* @param stats the original statements as they were parsed
* @param tparams the collective type parameters
Expand Down Expand Up @@ -934,6 +954,7 @@ object desugar {
stat match
case mdef: DefDef =>
cpy.DefDef(mdef)(
name = mdef.name.toExtensionName,
tparams = tparams ++ mdef.tparams,
vparamss = vparamss ::: mdef.vparamss,
).withMods(mdef.mods | Extension)
Expand Down Expand Up @@ -964,16 +985,21 @@ object desugar {
/** 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.
* 2. Check that the name does not start with `extension_` unless the
* method is an extension method.
* 3. 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(inventGivenOrExtensionName(impl))
def errPos = mdef.source.atSpan(mdef.nameSpan)
if (ctx.owner == defn.ScalaPackageClass && defn.reservedScalaClassNames.contains(name.toTypeName)) {
val kind = if (name.isTypeName) "class" else "object"
ctx.error(IllegalRedefinitionOfStandardKind(kind, name), mdef.sourcePos)
ctx.error(IllegalRedefinitionOfStandardKind(kind, name), errPos)
name = name.errorName
}
if name.isExtensionName && !mdef.mods.is(Extension) then
ctx.error(em"illegal method name: $name may not start with `extension_`", errPos)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we have a migration for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's not common enough to spend the effort.

name
}

Expand Down Expand Up @@ -1247,7 +1273,7 @@ object desugar {
}

private def isTopLevelDef(stat: Tree)(using Context): Boolean = stat match
case _: ValDef | _: PatDef | _: DefDef | _: Export => true
case _: ValDef | _: PatDef | _: DefDef | _: Export | _: ExtMethods => true
case stat: ModuleDef =>
stat.mods.isOneOf(GivenOrImplicit)
case stat: TypeDef =>
Expand Down
8 changes: 8 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
case class ContextBounds(bounds: TypeBoundsTree, cxBounds: List[Tree])(implicit @constructorOnly src: SourceFile) extends TypTree
case class PatDef(mods: Modifiers, pats: List[Tree], tpt: Tree, rhs: Tree)(implicit @constructorOnly src: SourceFile) extends DefTree
case class Export(expr: Tree, selectors: List[ImportSelector])(implicit @constructorOnly src: SourceFile) extends Tree
case class ExtMethods(tparams: List[TypeDef], vparamss: List[List[ValDef]], methods: List[DefDef])(implicit @constructorOnly src: SourceFile) extends Tree
case class MacroTree(expr: Tree)(implicit @constructorOnly src: SourceFile) extends Tree

case class ImportSelector(imported: Ident, renamed: Tree = EmptyTree, bound: Tree = EmptyTree)(implicit @constructorOnly src: SourceFile) extends Tree {
Expand Down Expand Up @@ -617,6 +618,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
case tree: Export if (expr eq tree.expr) && (selectors eq tree.selectors) => tree
case _ => finalize(tree, untpd.Export(expr, selectors)(tree.source))
}
def ExtMethods(tree: Tree)(tparams: List[TypeDef], vparamss: List[List[ValDef]], methods: List[DefDef])(using Context): Tree = tree match
case tree: ExtMethods if (tparams eq tree.tparams) && (vparamss eq tree.vparamss) && (methods == tree.methods) => tree
case _ => finalize(tree, untpd.ExtMethods(tparams, vparamss, methods)(tree.source))
def ImportSelector(tree: Tree)(imported: Ident, renamed: Tree, bound: Tree)(implicit ctx: Context): Tree = tree match {
case tree: ImportSelector if (imported eq tree.imported) && (renamed eq tree.renamed) && (bound eq tree.bound) => tree
case _ => finalize(tree, untpd.ImportSelector(imported, renamed, bound)(tree.source))
Expand Down Expand Up @@ -683,6 +687,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
cpy.PatDef(tree)(mods, transform(pats), transform(tpt), transform(rhs))
case Export(expr, selectors) =>
cpy.Export(tree)(transform(expr), selectors)
case ExtMethods(tparams, vparamss, methods) =>
cpy.ExtMethods(tree)(transformSub(tparams), vparamss.mapConserve(transformSub(_)), transformSub(methods))
case ImportSelector(imported, renamed, bound) =>
cpy.ImportSelector(tree)(transformSub(imported), transform(renamed), transform(bound))
case Number(_, _) | TypedSplice(_) =>
Expand Down Expand Up @@ -742,6 +748,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
this(this(this(x, pats), tpt), rhs)
case Export(expr, _) =>
this(x, expr)
case ExtMethods(tparams, vparamss, methods) =>
this(vparamss.foldLeft(this(x, tparams))(apply), methods)
case ImportSelector(imported, renamed, bound) =>
this(this(this(x, imported), renamed), bound)
case Number(_, _) =>
Expand Down
15 changes: 15 additions & 0 deletions compiler/src/dotty/tools/dotc/core/NameOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,21 @@ object NameOps {
else name.toTermName
}

/** Does this name start with `extension_`? */
def isExtensionName: Boolean = name match
case name: SimpleName => name.startsWith("extension_")
case _ => false

/** Add an `extension_` in front of this name */
def toExtensionName = termName("extension_" ++ name.toString)

/** Drop `extension_` in front of this name, if it has this prefix */
def dropExtension = name match
case name: SimpleName if name.startsWith("extension_") =>
name.drop("extension_".length)
case _ =>
name

/** The expanded name.
* This is the fully qualified name of `base` with `ExpandPrefixName` as separator,
* followed by `kind` and the name.
Expand Down
9 changes: 9 additions & 0 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1190,6 +1190,15 @@ object SymDenotations {
else if (this.exists) owner.enclosingMethod
else NoSymbol

/** The closest enclosing extension method containing this definition,
* provided the extension method appears in the same class.
*/
final def enclosingExtensionMethod(using Context): Symbol =
if this.isAllOf(ExtensionMethod) then symbol
else if this.isClass then NoSymbol
else if this.exists then owner.enclosingExtensionMethod
else NoSymbol

/** The top-level class containing this denotation,
* except for a toplevel module, where its module class is returned.
*/
Expand Down
Loading