Skip to content

Commit 11122ed

Browse files
committed
Allow two type parameter lists in extension methods
1 parent f3827dd commit 11122ed

File tree

28 files changed

+7659
-101
lines changed

28 files changed

+7659
-101
lines changed

compiler/src-bootstrapped/scala/quoted/runtime/impl/QuotesImpl.scala

Lines changed: 2889 additions & 0 deletions
Large diffs are not rendered by default.

compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala renamed to compiler/src-non-bootstrapped/scala/quoted/runtime/impl/QuotesImpl.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
6262

6363
/** Convert this to an `quoted.Expr[X]` if this expression is a valid expression of type `X` or throws */
6464
def asExprOf(using scala.quoted.Type[X]): scala.quoted.Expr[X] = {
65-
if isExprOf[X] then
65+
if this.isExprOf[X](self) then
6666
self.asInstanceOf[scala.quoted.Expr[X]]
6767
else
6868
throw Exception(

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

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -906,33 +906,24 @@ object desugar {
906906
/** Transform extension construct to list of extension methods */
907907
def extMethods(ext: ExtMethods)(using Context): Tree = flatTree {
908908
for mdef <- ext.methods yield
909-
var extParamss = ext.paramss
910-
var mdefParamss = mdef.paramss
911-
if mdef.leadingTypeParams.nonEmpty then
912-
report.error("extension method cannot have type parameters here, all type parameters go after `extension`",
913-
mdef.leadingTypeParams.head.srcPos)
914-
extParamss = extParamss match
915-
case TypeDefs(tparams) :: paramss1 => (tparams ++ mdef.leadingTypeParams) :: paramss1
916-
case _ => mdef.leadingTypeParams :: extParamss
917-
mdefParamss = mdef.trailingParamss
918909
defDef(
919910
cpy.DefDef(mdef)(
920911
name = normalizeName(mdef, ext).asTermName,
921912
paramss = mdef.paramss match
922913
case params1 :: paramss1 if mdef.name.isRightAssocOperatorName =>
923914
def badRightAssoc(problem: String) =
924915
report.error(i"right-associative extension method $problem", mdef.srcPos)
925-
extParamss ++ mdefParamss
916+
ext.paramss ++ mdef.paramss
926917
params1 match
927918
case ValDefs(vparam :: Nil) =>
928919
if !vparam.mods.is(Given) then
929-
val (leadingUsing, otherExtParamss) = extParamss.span(isUsingOrTypeParamClause)
920+
val (leadingUsing, otherExtParamss) = ext.paramss.span(isUsingOrTypeParamClause)
930921
leadingUsing ::: params1 :: otherExtParamss ::: paramss1
931922
else badRightAssoc("cannot start with using clause")
932923
case _ =>
933924
badRightAssoc("must start with a single parameter")
934925
case _ =>
935-
extParamss ++ mdefParamss
926+
ext.paramss ++ mdef.paramss
936927
).withMods(mdef.mods | ExtensionMethod)
937928
)
938929
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,11 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
564564
}
565565
}
566566

567+
def isExtMethodApply(tree: Tree)(using Context): Boolean = methPart(tree) match
568+
case Inlined(call, _, _) => isExtMethodApply(call)
569+
case tree @ Select(qual, nme.apply) => tree.symbol.is(ExtensionMethod) || isExtMethodApply(qual)
570+
case tree => tree.symbol.is(ExtensionMethod)
571+
567572
/** Is symbol potentially a getter of a mutable variable?
568573
*/
569574
def mayBeVarGetter(sym: Symbol)(using Context): Boolean = {

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
647647
keywordStr("${") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}")
648648
case tree: Applications.IntegratedTypeArgs =>
649649
toText(tree.app) ~ Str("(with integrated type args)").provided(printDebug)
650+
case tree: Applications.ExtMethodApply =>
651+
toText(tree.app) ~ Str("(ext method apply)").provided(printDebug)
650652
case Thicket(trees) =>
651653
"Thicket {" ~~ toTextGlobal(trees, "\n") ~~ "}"
652654
case MacroTree(call) =>

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

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -196,12 +196,8 @@ object Applications {
196196
def wrapDefs(defs: mutable.ListBuffer[Tree], tree: Tree)(using Context): Tree =
197197
if (defs != null && defs.nonEmpty) tpd.Block(defs.toList, tree) else tree
198198

199-
/** A wrapper indicating that its `app` argument has already integrated the type arguments
200-
* of the expected type, provided that type is a (possibly ignored) PolyProto.
201-
* I.e., if the expected type is a PolyProto, then `app` will be a `TypeApply(_, args)` where
202-
* `args` are the type arguments of the expected type.
203-
*/
204-
class IntegratedTypeArgs(val app: Tree)(implicit @constructorOnly src: SourceFile) extends ProxyTree {
199+
abstract class AppProxy(implicit @constructorOnly src: SourceFile) extends ProxyTree {
200+
def app: Tree
205201
override def span = app.span
206202

207203
def forwardTo = app
@@ -210,6 +206,13 @@ object Applications {
210206
def productElement(n: Int): Any = app.productElement(n)
211207
}
212208

209+
/** A wrapper indicating that its `app` argument has already integrated the type arguments
210+
* of the expected type, provided that type is a (possibly ignored) PolyProto.
211+
* I.e., if the expected type is a PolyProto, then `app` will be a `TypeApply(_, args)` where
212+
* `args` are the type arguments of the expected type.
213+
*/
214+
class IntegratedTypeArgs(val app: Tree)(implicit @constructorOnly src: SourceFile) extends AppProxy
215+
213216
/** The unapply method of this extractor also recognizes IntegratedTypeArgs in closure blocks.
214217
* This is necessary to deal with closures as left arguments of extension method applications.
215218
* A test case is i5606.scala
@@ -225,12 +228,10 @@ object Applications {
225228

226229
/** A wrapper indicating that its argument is an application of an extension method.
227230
*/
228-
class ExtMethodApply(app: Tree)(implicit @constructorOnly src: SourceFile)
229-
extends IntegratedTypeArgs(app) {
230-
overwriteType(WildcardType)
231+
class ExtMethodApply(val app: Tree)(implicit @constructorOnly src: SourceFile) extends AppProxy:
232+
overwriteType(app.tpe)
231233
// ExtMethodApply always has wildcard type in order not to prompt any further adaptations
232234
// such as eta expansion before the method is fully applied.
233-
}
234235
}
235236

236237
trait Applications extends Compatibility {
@@ -2146,9 +2147,6 @@ trait Applications extends Compatibility {
21462147
// Always hide expected member to allow for chained extensions (needed for i6900.scala)
21472148
case _: SelectionProto =>
21482149
(tree, IgnoredProto(currentPt))
2149-
case PolyProto(targs, restpe) =>
2150-
val tree1 = untpd.TypeApply(tree, targs.map(untpd.TypedSplice(_)))
2151-
normalizePt(tree1, restpe)
21522150
case _ =>
21532151
(tree, currentPt)
21542152

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -937,7 +937,7 @@ trait Implicits:
937937
case Select(qual, _) => apply(x, qual)
938938
case Apply(fn, _) => apply(x, fn)
939939
case TypeApply(fn, _) => apply(x, fn)
940-
case tree: Applications.IntegratedTypeArgs => apply(x, tree.app)
940+
case tree: Applications.AppProxy => apply(x, tree.app)
941941
case _: This => false
942942
case _ => foldOver(x, tree)
943943
}

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,8 @@ class Typer extends Namer
553553
case _: PolyProto => qual // keep the IntegratedTypeArgs to strip at next typedTypeApply
554554
case _ => app
555555
}
556+
case qual: ExtMethodApply =>
557+
qual.app
556558
case qual =>
557559
val select = assignType(cpy.Select(tree)(qual, tree.name), qual)
558560
val select1 = toNotNullTermRef(select, pt)
@@ -2603,18 +2605,16 @@ class Typer extends Namer
26032605
}
26042606

26052607
/** Interpolate and simplify the type of the given tree. */
2606-
protected def simplify(tree: Tree, pt: Type, locked: TypeVars)(using Context): tree.type = {
2607-
if (!tree.denot.isOverloaded &&
2608-
// for overloaded trees: resolve overloading before simplifying
2609-
!tree.isInstanceOf[Applications.IntegratedTypeArgs])
2610-
// don't interpolate in the middle of an extension method application
2611-
if (!tree.tpe.widen.isInstanceOf[MethodOrPoly] // wait with simplifying until method is fully applied
2612-
|| tree.isDef) { // ... unless tree is a definition
2608+
protected def simplify(tree: Tree, pt: Type, locked: TypeVars)(using Context): tree.type =
2609+
if !tree.denot.isOverloaded // for overloaded trees: resolve overloading before simplifying
2610+
&& !tree.isInstanceOf[Applications.AppProxy] // don't interpolate in the middle of an extension method application
2611+
then
2612+
if !tree.tpe.widen.isInstanceOf[MethodOrPoly] // wait with simplifying until method is fully applied
2613+
|| tree.isDef // ... unless tree is a definition
2614+
then
26132615
interpolateTypeVars(tree, pt, locked)
26142616
tree.overwriteType(tree.tpe.simplified)
2615-
}
26162617
tree
2617-
}
26182618

26192619
protected def makeContextualFunction(tree: untpd.Tree, pt: Type)(using Context): Tree = {
26202620
val defn.FunctionOf(formals, _, true, _) = pt.dropDependentRefinement

compiler/src/dotty/tools/repl/ReplCompiler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ class ReplCompiler extends Compiler {
280280
if (errorsAllowed || !ctx.reporter.hasErrors)
281281
unwrapped(unit.tpdTree, src)
282282
else
283-
ctx.reporter.removeBufferedMessages.errors[tpd.ValDef] // Workaround #4988
283+
ctx.reporter.removeBufferedMessages.errors // Workaround #4988
284284
}
285285
}
286286
}

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,6 @@ class CompilationTests {
157157
compileFile("tests/neg-custom-args/missing-alpha.scala", defaultOptions.and("-Yrequire-targetName", "-Xfatal-warnings")),
158158
compileFile("tests/neg-custom-args/wildcards.scala", defaultOptions.and("-source", "3.1", "-deprecation", "-Xfatal-warnings")),
159159
compileFile("tests/neg-custom-args/indentRight.scala", defaultOptions.and("-noindent", "-Xfatal-warnings")),
160-
compileFile("tests/neg-custom-args/extmethods-tparams.scala", defaultOptions.and("-deprecation", "-Xfatal-warnings")),
161160
compileDir("tests/neg-custom-args/adhoc-extension", defaultOptions.and("-source", "3.1", "-feature", "-Xfatal-warnings")),
162161
compileFile("tests/neg/i7575.scala", defaultOptions.withoutLanguageFeatures.and("-language:_")),
163162
compileFile("tests/neg-custom-args/kind-projector.scala", defaultOptions.and("-Ykind-projector")),
@@ -250,7 +249,7 @@ class CompilationTests {
250249
val tastyCoreSources = sources(Paths.get("tasty/src"))
251250
val tastyCore = compileList("tastyCore", tastyCoreSources, opt)(tastyCoreGroup)
252251

253-
val compilerSources = sources(Paths.get("compiler/src"))
252+
val compilerSources = sources(Paths.get("compiler/src")) ++ sources(Paths.get("compiler/src-bootstrapped"))
254253
val compilerManagedSources = sources(Properties.dottyCompilerManagedSources)
255254

256255
val dotty1 = compileList("dotty1", compilerSources ++ compilerManagedSources, opt)(dotty1Group)

docs/docs/reference/contextual/extension-methods.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,10 @@ The three definitions above translate to
5858
Note the swap of the two parameters `x` and `xs` when translating
5959
the right-associative operator `+:` to an extension method. This is analogous
6060
to the implementation of right binding operators as normal methods. The Scala
61-
compiler preprocesses an infix operation `x +: xs` to `xs.+:(x)`, so the extension method ends up being applied to the sequence as first argument (in other words, the two swaps cancel each other out).
61+
compiler preprocesses an infix operation `x +: xs` to `xs.+:(x)`, so the extension
62+
method ends up being applied to the sequence as first argument (in other words, the
63+
two swaps cancel each other out). See [here for details](./right-associative-extension-methods.html).
64+
6265
### Generic Extensions
6366

6467
It is also possible to extend generic types by adding type parameters to an extension. For instance:

0 commit comments

Comments
 (0)