diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index c2c29f60ed92..d1cfbddfb5ed 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -859,21 +859,22 @@ object desugar { * given object name extends parents { self => body' } * * where every definition in `body` is expanded to an extension method - * taking type parameters `tparams` and a leading parameter `(x: T)`. - * See: makeExtensionDef + * taking type parameters `tparams` and a leading paramter `(x: T)`. + * See: collectiveExtensionBody */ def moduleDef(mdef: ModuleDef)(implicit ctx: Context): Tree = { val impl = mdef.impl val mods = mdef.mods impl.constr match { - case DefDef(_, tparams, (vparams @ (vparam :: Nil)) :: givenParamss, _, _) => + case DefDef(_, tparams, vparamss @ (vparam :: Nil) :: givenParamss, _, _) => + // Transform collective extension assert(mods.is(Given)) return moduleDef( cpy.ModuleDef(mdef)( mdef.name, cpy.Template(impl)( constr = emptyConstructor, - body = impl.body.map(makeExtensionDef(_, tparams, vparams, givenParamss))))) + body = collectiveExtensionBody(impl.body, tparams, vparamss)))) case _ => } @@ -916,43 +917,40 @@ object desugar { } } - /** Given tpe parameters `Ts` (possibly empty) and a leading value parameter `(x: T)`, - * map a method definition + /** Transform the statements of a collective extension + * @param stats the original statements as they were parsed + * @param tparams the collective type parameters + * @param vparamss the collective value parameters, consisting + * of a single leading value parameter, followed by + * zero or more context parameter clauses * - * def foo [Us] paramss ... + * Note: It is already assured by Parser.checkExtensionMethod that all + * statements conform to requirements. * - * to + * Each method in stats is transformed into an extension method. Example: + * + * extension on [Ts](x: T)(using C): + * def f(y: T) = ??? + * def g(z: T) = f(z) * - * def foo[Ts ++ Us](x: T) parammss ... + * is turned into * - * If the given member `mdef` is not of this form, flag it as an error. + * extension: + * def f[Ts](x: T)(using C)(y: T) = ??? + * def g[Ts](x: T)(using C)(z: T) = f(z) */ - - def makeExtensionDef(mdef: Tree, tparams: List[TypeDef], leadingParams: List[ValDef], - givenParamss: List[List[ValDef]])(using ctx: Context): Tree = { - mdef match { - case mdef: DefDef => - if (mdef.mods.is(Extension)) { - ctx.error(NoExtensionMethodAllowed(mdef), mdef.sourcePos) - mdef - } else { - if (tparams.nonEmpty && mdef.tparams.nonEmpty) then - ctx.error(ExtensionMethodCannotHaveTypeParams(mdef), mdef.tparams.head.sourcePos) - mdef - else cpy.DefDef(mdef)( + def collectiveExtensionBody(stats: List[Tree], + tparams: List[TypeDef], vparamss: List[List[ValDef]])(using ctx: Context): List[Tree] = + for stat <- stats yield + stat match + case mdef: DefDef => + cpy.DefDef(mdef)( tparams = tparams ++ mdef.tparams, - vparamss = leadingParams :: givenParamss ::: mdef.vparamss + vparamss = vparamss ::: mdef.vparamss, ).withMods(mdef.mods | Extension) - } - case mdef: Import => - mdef - case mdef if !mdef.isEmpty => { - ctx.error(ExtensionCanOnlyHaveDefs(mdef), mdef.sourcePos) - mdef - } - case mdef => mdef - } - } + case mdef => + mdef + end collectiveExtensionBody /** Transforms * diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index f21abcb53d1b..c5b033342460 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -301,7 +301,7 @@ object Flags { /** A method that has default params */ val (_, DefaultParameterized @ _, _) = newFlags(27, "") - /** An extension method */ + /** An extension method, or a collective extension instance */ val (_, Extension @ _, _) = newFlags(28, "") /** An inferable (`given`) parameter */ @@ -499,14 +499,14 @@ object Flags { /** Flags that can apply to a module val */ val RetainedModuleValFlags: FlagSet = RetainedModuleValAndClassFlags | - Override | Final | Method | Implicit | Given | Lazy | + Override | Final | Method | Implicit | Given | Lazy | Extension | Accessor | AbsOverride | StableRealizable | Captured | Synchronized | Erased /** Flags that can apply to a module class */ val RetainedModuleClassFlags: FlagSet = RetainedModuleValAndClassFlags | Enum /** Flags retained in export forwarders */ - val RetainedExportFlags = Given | Implicit | Extension | Inline + val RetainedExportFlags = Given | Implicit | Inline /** Flags that apply only to classes */ val ClassOnlyFlags = Sealed | Open | Abstract.toTypeFlags diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 84eca75e84c2..d01868ab5cb2 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -931,7 +931,11 @@ object Parsers { lookahead.nextToken() if lookahead.isIdent && !lookahead.isIdent(nme.on) then lookahead.nextToken() + if lookahead.isNewLine then + lookahead.nextToken() lookahead.isIdent(nme.on) + || lookahead.token == LBRACE + || lookahead.token == COLON /* --------- OPERAND/OPERATOR STACK --------------------------------------- */ @@ -3470,6 +3474,23 @@ object Parsers { Template(constr, parents, Nil, EmptyValDef, Nil) } + def checkExtensionMethod(tparams: List[Tree], + vparamss: List[List[Tree]], stat: Tree): Unit = stat match { + case stat: DefDef => + if stat.mods.is(Extension) && vparamss.nonEmpty then + syntaxError(i"no extension method allowed here since leading parameter was already given", stat.span) + else if !stat.mods.is(Extension) && vparamss.isEmpty then + syntaxError(i"an extension method is required here", stat.span) + else if tparams.nonEmpty && stat.tparams.nonEmpty then + syntaxError(i"extension method cannot have type parameters since some were already given previously", + stat.tparams.head.span) + else if stat.rhs.isEmpty then + syntaxError(i"extension method cannot be abstract", stat.span) + case EmptyTree => + case stat => + syntaxError(i"extension clause can only define methods", stat.span) + } + /** GivenDef ::= [GivenSig] [‘_’ ‘<:’] Type ‘=’ Expr * | [GivenSig] ConstrApps [TemplateBody] * GivenSig ::= [id] [DefTypeParamClause] {UsingParamClauses} ‘as’ @@ -3516,22 +3537,27 @@ object Parsers { finalizeDef(gdef, mods1, start) } - /** ExtensionDef ::= [id] ‘on’ ExtParamClause {UsingParamClause} ExtMethods + /** ExtensionDef ::= [id] [‘on’ ExtParamClause {UsingParamClause}] TemplateBody */ def extensionDef(start: Offset, mods: Modifiers): ModuleDef = in.nextToken() val name = if isIdent && !isIdent(nme.on) then ident() else EmptyTermName in.endMarkerScope(if name.isEmpty then nme.extension else name) { - if !isIdent(nme.on) then syntaxErrorOrIncomplete("`on` expected") - if isIdent(nme.on) then in.nextToken() - val tparams = typeParamClauseOpt(ParamOwner.Def) - val extParams = paramClause(0, prefix = true) - val givenParamss = paramClauses(givenOnly = true) + val (tparams, vparamss, extensionFlag) = + if isIdent(nme.on) then + in.nextToken() + val tparams = typeParamClauseOpt(ParamOwner.Def) + val extParams = paramClause(0, prefix = true) + val givenParamss = paramClauses(givenOnly = true) + (tparams, extParams :: givenParamss, Extension) + else + (Nil, Nil, EmptyFlags) possibleTemplateStart() if !in.isNestedStart then syntaxError("Extension without extension methods") - val templ = templateBodyOpt(makeConstructor(tparams, extParams :: givenParamss), Nil, Nil) + val templ = templateBodyOpt(makeConstructor(tparams, vparamss), Nil, Nil) + templ.body.foreach(checkExtensionMethod(tparams, vparamss, _)) val edef = ModuleDef(name, templ) - finalizeDef(edef, addFlag(mods, Given), start) + finalizeDef(edef, addFlag(mods, Given | extensionFlag), start) } /* -------- TEMPLATES ------------------------------------------- */ diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index cee9866c8a02..ab57c3a3bc93 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -220,4 +220,7 @@ class SymUtils(val self: Symbol) extends AnyVal { /** Is symbol a splice operation? */ def isSplice(implicit ctx: Context): Boolean = self == defn.InternalQuoted_exprSplice || self == defn.QuotedType_splice + + def isCollectiveExtensionClass(using Context): Boolean = + self.is(ModuleClass) && self.sourceModule.is(Extension, butNot = Method) } diff --git a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala index 47749a28265c..978c8466c75e 100644 --- a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala +++ b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala @@ -205,7 +205,7 @@ trait ImportSuggestions: .alternatives .map(mbr => TermRef(site, mbr.symbol)) .filter(ref => - ref.symbol.is(Extension) + ref.symbol.isAllOf(ExtensionMethod) && isApplicableMethodRef(ref, argType :: Nil, WildcardType)) .headOption diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index c2e81b18c348..0f74dbc456bc 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1096,7 +1096,8 @@ class Namer { typer: Typer => (StableRealizable, ExprType(path.tpe.select(sym))) else (EmptyFlags, mbr.info.ensureMethodic) - val mbrFlags = Exported | Method | Final | maybeStable | sym.flags & RetainedExportFlags + var mbrFlags = Exported | Method | Final | maybeStable | sym.flags & RetainedExportFlags + if sym.isAllOf(ExtensionMethod) then mbrFlags |= Extension val forwarderName = checkNoConflict(alias, isPrivate = false, span) ctx.newSymbol(cls, forwarderName, mbrFlags, mbrInfo, coord = span) } diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index 04795bc28a3d..7b593e529aec 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -170,7 +170,7 @@ object Nullables: case info :: infos1 => if info.asserted.contains(ref) then true else if info.retracted.contains(ref) then false - else impliesNotNull(infos1)(ref) + else infos1.impliesNotNull(ref) case _ => false diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 681ef893e58b..7eebc6aebfe4 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -394,9 +394,9 @@ object RefChecks { overrideError("is erased, cannot override non-erased member") else if (other.is(Erased) && !member.isOneOf(Erased | Inline)) // (1.9.1) overrideError("is not erased, cannot override erased member") - else if (member.is(Extension) && !other.is(Extension)) // (1.9.2) + else if (member.isAllOf(ExtensionMethod) && !other.isAllOf(ExtensionMethod)) // (1.9.2) overrideError("is an extension method, cannot override a normal method") - else if (other.is(Extension) && !member.is(Extension)) // (1.9.2) + else if (other.isAllOf(ExtensionMethod) && !member.isAllOf(ExtensionMethod)) // (1.9.2) overrideError("is a normal method, cannot override an extension method") else if ((member.isInlineMethod || member.isScala2Macro) && other.is(Deferred) && member.extendedOverriddenSymbols.forall(_.is(Deferred))) // (1.10) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 2e5afd3cde0f..0bdf6e2c75e0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -401,6 +401,19 @@ class Typer extends Namer if (name == nme.ROOTPKG) return tree.withType(defn.RootPackage.termRef) + /** Convert a reference `f` to an extension method in a collective extension + * on parameter `x` to `x.f` + */ + def extensionMethodSelect(xmethod: Symbol): untpd.Tree = + val leadParamName = xmethod.info.paramNamess.head.head + def isLeadParam(sym: Symbol) = + sym.is(Param) && sym.owner.owner == xmethod.owner && sym.name == leadParamName + def leadParam(ctx: Context): Symbol = + ctx.scope.lookupAll(leadParamName).find(isLeadParam) match + case Some(param) => param + case None => leadParam(ctx.outersIterator.dropWhile(_.scope eq ctx.scope).next) + untpd.cpy.Select(tree)(untpd.ref(leadParam(ctx).termRef), name) + val rawType = { val saved1 = unimported val saved2 = foundUnderScala2 @@ -441,8 +454,14 @@ class Typer extends Namer errorType(new MissingIdent(tree, kind, name.show), tree.sourcePos) val tree1 = ownType match { - case ownType: NamedType if !prefixIsElidable(ownType) => - ref(ownType).withSpan(tree.span) + case ownType: NamedType => + val sym = ownType.symbol + if sym.isAllOf(ExtensionMethod) + && sym.owner.isCollectiveExtensionClass + && ctx.owner.isContainedIn(sym.owner) + then typed(extensionMethodSelect(sym), pt) + else if prefixIsElidable(ownType) then tree.withType(ownType) + else ref(ownType).withSpan(tree.span) case _ => tree.withType(ownType) } diff --git a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala index 9dee80d55271..e1c017ad161c 100644 --- a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala +++ b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala @@ -1840,74 +1840,6 @@ class ErrorMessagesTests extends ErrorMessagesTest { assertEquals("given x @ String", x.show) } - @Test def extensionMethodsNotAllowed = - checkMessagesAfter(RefChecks.name) { - """object Test { - | extension on[T] (t: T) { - | def (c: T).f: T = ??? - | } - |} - """.stripMargin - } - .expect { (ictx, messages) ⇒ - implicit val ctx: Context = ictx - assertMessageCount(1, messages) - val errorMsg = messages.head.msg - val NoExtensionMethodAllowed(x) :: Nil = messages - assertEquals("No extension method allowed here, since collective parameters are given", errorMsg) - assertEquals("def (c: T) f: T = ???", x.show) - } - - @Test def extensionMethodTypeParamsNotAllowed = - checkMessagesAfter(RefChecks.name) { - """object Test { - | extension on[T] (t: T) { - | def f[U](u: U): T = ??? - | } - |} - """.stripMargin - } - .expect { (ictx, messages) ⇒ - implicit val ctx: Context = ictx - assertMessageCount(1, messages) - val errorMsg = messages.head.msg - val ExtensionMethodCannotHaveTypeParams(x) :: Nil = messages - assertEquals("Extension method cannot have type parameters since some were already given previously", errorMsg) - assertEquals("def f[U](u: U): T = ???", x.show) - } - - @Test def extensionMethodCanOnlyHaveDefs = - checkMessagesAfter(RefChecks.name) { - """object Test { - | extension on[T] (t: T) { - | val v: T = t - | } - |} - """.stripMargin - } - .expect { (ictx, messages) ⇒ - implicit val ctx: Context = ictx - assertMessageCount(1, messages) - val errorMsg = messages.head.msg - val ExtensionCanOnlyHaveDefs(x) :: Nil = messages - assertEquals("Only methods allowed here, since collective parameters are given", errorMsg) - assertEquals("val v: T = t", x.show) - } - - @Test def anonymousInstanceMustImplementAType = - checkMessagesAfter(RefChecks.name) { - """object Test { - | extension on[T] (t: T) { } - |} - """.stripMargin - } - .expect { (ictx, messages) ⇒ - implicit val ctx: Context = ictx - assertMessageCount(1, messages) - val errorMsg = messages.head.msg - assertEquals("anonymous instance must implement a type or have at least one extension method", errorMsg) - } - @Test def typeSplicesInValPatterns = checkMessagesAfter(RefChecks.name) { s"""import scala.quoted._ diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 6a1a169eece9..ee59cab8d50f 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -386,7 +386,8 @@ EnumDef ::= id ClassConstr InheritClauses EnumBody GivenDef ::= [GivenSig] [‘_’ ‘<:’] Type ‘=’ Expr | [GivenSig] ConstrApps [TemplateBody] GivenSig ::= [id] [DefTypeParamClause] {UsingParamClause} ‘as’ -ExtensionDef ::= [id] ‘on’ ExtParamClause {WithParamsOrTypes} ExtMethods +ExtensionDef ::= [id] [‘on’ ExtParamClause {UsingParamClause}] + TemplateBody ExtMethods ::= [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’ Template ::= InheritClauses [TemplateBody] Template(constr, parents, self, stats) diff --git a/docs/docs/reference/contextual/extension-methods.md b/docs/docs/reference/contextual/extension-methods.md index a2035f111fe7..ea75387106a5 100644 --- a/docs/docs/reference/contextual/extension-methods.md +++ b/docs/docs/reference/contextual/extension-methods.md @@ -124,59 +124,121 @@ If an extension method has type parameters, they come immediately after the `def ```scala List(1, 2, 3).second[Int] ``` -### Collective Extensions -A collective extension defines one or more concrete methods that have the same type parameters -and prefix parameter. Examples: +### Extension Instances +It is quite common to wrap one or more extension methods in a given instance, +in order to make them available as methods without needing to be imported explicitly. +This pattern is supported by a special `extension` syntax. Example: ```scala -extension stringOps on (xs: Seq[String]) { - def longestStrings: Seq[String] = { +extension ops { + def (xs: Seq[String]).longestStrings: Seq[String] = { val maxLength = xs.map(_.length).max xs.filter(_.length == maxLength) } + def (xs: Seq[String]).longestString: String = xs.longestStrings.head + def [T](xs: List[T]).second: T = xs.tail.head +} +``` +An extension instance can only contain extension methods. Other definitions are not allowed. The name `ops` +of the extension is optional. It can be left out: +```scala +extension { + def (xs: Seq[String]).longestStrings: Seq[String] = ... + def [T](xs: List[T]).second: T = ... +} +``` +If the name of an extension is not explicitly given, it is synthesized from the name and type of the first implemented extension method. + +Extension instances map directly to given instances. The `ops` extension above +would expand to +```scala +given ops as AnyRef { + def (xs: Seq[String]).longestStrings: Seq[String] = ... + def (xs: Seq[String]).longestString: String = ... + def [T](xs: List[T]).second: T = ... +} +``` +The type "implemented" by this given instance is `AnyRef`, which +is not a type one can summon by itself. This means that the instance can +only be used for its extension methods. + +### Collective Extensions + +Sometimes, one wants to define several extension methods that share the same +left-hand parameter type. In this case one can "pull out" the common parameters +into the extension instance itself. Examples: +```scala +extension stringOps on (ss: Seq[String]) { + def longestStrings: Seq[String] = { + val maxLength = ss.map(_.length).max + ss.filter(_.length == maxLength) + } + def longestString: String = longestStrings.head } extension listOps on [T](xs: List[T]) { - def second = xs.tail.head - def third: T = xs.tail.tail.head + def second: T = xs.tail.head + def third: T = xs.tail.second } extension on [T](xs: List[T])(using Ordering[T]) { def largest(n: Int) = xs.sorted.takeRight(n) } ``` -If an extension is anonymous (as in the last clause), its name is synthesized from the name of the first defined extension method. - -The extensions above are equivalent to the following regular given instances where the implemented parent is `AnyRef` and the leading parameters are repeated in each extension method definition: +Collective extensions like these are a shorthand for extension instances where +the parameters following the `on` are repeated for each implemented method. +For instance, the collective extensions above expand to the following extension instances: ```scala -given stringOps as AnyRef { - def (xs: Seq[String]).longestStrings: Seq[String] = { - val maxLength = xs.map(_.length).max - xs.filter(_.length == maxLength) +extension stringOps { + def (ss: Seq[String]).longestStrings: Seq[String] = { + val maxLength = ss.map(_.length).max + ss.filter(_.length == maxLength) } + def (ss: Seq[String]).longestString: String = + ss.longestStrings.head } -given listOps as AnyRef { - def [T](xs: List[T]).second = xs.tail.head - def [T](xs: List[T]).third: T = xs.tail.tail.head +extension listOps { + def [T](xs: List[T]).second: T = xs.tail.head + def [T](xs: List[T]).third: T = xs.tail.second } -given extension_largest_List_T as AnyRef { +extension { def [T](xs: List[T]).largest(using Ordering[T])(n: Int) = xs.sorted.takeRight(n) } ``` +One special tweak is shown in the `longestString` method of the `stringOps` extension. It's original definition was +```scala +def longestString: String = longestStrings.head +``` +This uses `longestStrings` as an implicit extension method call on the joint +parameter `ss`. The usage is made explicit when translating the method: +```scala +def (ss: Seq[String]).longestString: String = + ss.longestStrings.head +``` ### Syntax Here are the syntax changes for extension methods and collective extensions relative -to the [current syntax](../../internals/syntax.md). `extension` is a soft keyword, recognized only in tandem with `on`. It can be used as an identifier everywhere else. - +to the [current syntax](../../internals/syntax.md). ``` DefSig ::= ... | ExtParamClause [nl] [‘.’] id DefParamClauses ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’ TmplDef ::= ... | ‘extension’ ExtensionDef -ExtensionDef ::= [id] ‘on’ ExtParamClause {GivenParamClause} ExtMethods -ExtMethods ::= ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ +ExtensionDef ::= [id] [‘on’ ExtParamClause {GivenParamClause}] TemplateBody ``` +The template body of an extension must consist only of extension method definitions for a regular +extension instance, and only of normal method definitions for a collective extension instance. +It must not be empty. + +`extension` and `on` are soft keywords, recognized only when they appear at the start of a +statement in one of the patterns +```scala +extension on ... +extension on ... +extension { ... +extension { ... +``` \ No newline at end of file diff --git a/tests/neg/extension-cannot-have-type.check b/tests/neg/extension-cannot-have-type.check index b98355683c05..b7bfc18ad778 100644 --- a/tests/neg/extension-cannot-have-type.check +++ b/tests/neg/extension-cannot-have-type.check @@ -1,6 +1,4 @@ --- [E151] Syntax Error: tests/neg/extension-cannot-have-type.scala:3:10 ------------------------------------------------ +-- Error: tests/neg/extension-cannot-have-type.scala:3:10 -------------------------------------------------------------- 3 | def f[U](u: U): T = ??? // error : extension method cannot have type params | ^ - | Extension method cannot have type parameters since some were already given previously - -longer explanation available when compiling with `-explain` + | extension method cannot have type parameters since some were already given previously diff --git a/tests/neg/extension-method-not-allowed.check b/tests/neg/extension-method-not-allowed.check index fd109a3e921d..8886f484b869 100644 --- a/tests/neg/extension-method-not-allowed.check +++ b/tests/neg/extension-method-not-allowed.check @@ -1,6 +1,4 @@ --- [E150] Syntax Error: tests/neg/extension-method-not-allowed.scala:3:8 ----------------------------------------------- +-- Error: tests/neg/extension-method-not-allowed.scala:3:8 ------------------------------------------------------------- 3 | def (c: T).f: T = ??? // error : No extension method allowed here | ^^^^^^^^^^^^^^^^^^^^^ - | No extension method allowed here, since collective parameters are given - -longer explanation available when compiling with `-explain` + | no extension method allowed here since leading parameter was already given diff --git a/tests/neg/extensions-can-only-have-defs.check b/tests/neg/extensions-can-only-have-defs.check index 7fd29b92aba8..c155678f6fed 100644 --- a/tests/neg/extensions-can-only-have-defs.check +++ b/tests/neg/extensions-can-only-have-defs.check @@ -1,6 +1,4 @@ --- [E152] Syntax Error: tests/neg/extensions-can-only-have-defs.scala:3:8 ---------------------------------------------- +-- Error: tests/neg/extensions-can-only-have-defs.scala:3:8 ------------------------------------------------------------ 3 | val v: T = ??? // error : extensions can only have defs | ^^^^^^^^^^^^^^ - | Only methods allowed here, since collective parameters are given - -longer explanation available when compiling with `-explain` + | extension clause can only define methods diff --git a/tests/neg/i6900.scala b/tests/neg/i6900.scala index a81b4ba58350..c9ead6a99051 100644 --- a/tests/neg/i6900.scala +++ b/tests/neg/i6900.scala @@ -4,12 +4,12 @@ object Test2 { extension on [A](a: A): def foo[C]: C => A = _ => a // error: extension method cannot have type parameters - 1.foo.foo // error: foo is undefined + 1.foo.foo // ... but have to pass 2 parameters - 1.foo.foo[Any => Int, String] // error: foo is undefined - 1.foo[Int, String].foo // error: foo is undefined - 1.foo[Int, String].foo[String => Int, String] // error: foo is undefined + 1.foo.foo[Any => Int, String] + 1.foo[Int, String].foo + 1.foo[Int, String].foo[String => Int, String] } diff --git a/tests/pos/reference/extension-methods.scala b/tests/pos/reference/extension-methods.scala index 8fe07d523e7a..78ba2233464f 100644 --- a/tests/pos/reference/extension-methods.scala +++ b/tests/pos/reference/extension-methods.scala @@ -56,14 +56,25 @@ object ExtMethods: extension on [T](xs: List[T])(using Ordering[T]): def largest(n: Int) = xs.sorted.takeRight(n) - given stringOps1 as AnyRef { + extension ops: + def (xs: Seq[String]).longestStrings: Seq[String] = + val maxLength = xs.map(_.length).max + xs.filter(_.length == maxLength) + def (xs: Seq[String]).longestString: String = xs.longestStrings.head + def [T](xs: List[T]).second: T = xs.tail.head + + extension: + def [T](xs: List[T]) longest (using Ordering[T])(n: Int) = + xs.sorted.takeRight(n) + + given stringOps2 as AnyRef { def (xs: Seq[String]).longestStrings: Seq[String] = { val maxLength = xs.map(_.length).max xs.filter(_.length == maxLength) } } - given listOps1 as AnyRef { + given listOps2 as AnyRef { def [T](xs: List[T]) second = xs.tail.head def [T](xs: List[T]) third: T = xs.tail.tail.head } diff --git a/tests/run/collective-extensions.scala b/tests/run/collective-extensions.scala new file mode 100644 index 000000000000..9c8c2d4de04d --- /dev/null +++ b/tests/run/collective-extensions.scala @@ -0,0 +1,23 @@ +extension on (x: String): + def foo(y: String): String = x ++ y + def bar(y: String): String = foo(y) + def baz(y: String): String = + val x = y + bar(x) + def bam(y: String): String = this.baz(x)(y) + def ban(foo: String): String = x + foo + def bao(y: String): String = + val bam = "ABC" + x ++ y ++ bam + + def app(n: Int, suffix: String): String = + if n == 0 then x ++ suffix + else app(n - 1, suffix ++ suffix) + +@main def Test = + assert("abc".bar("def") == "abcdef") + assert("abc".baz("def") == "abcdef") + assert("abc".bam("def") == "abcdef") + assert("abc".ban("def") == "abcdef") + assert("abc".bao("def") == "abcdefABC") + assert("abc".app(3, "!") == "abc!!!!!!!!") diff --git a/tests/run/extmethod-overload.scala b/tests/run/extmethod-overload.scala index edb88dd90990..7ae67268957c 100644 --- a/tests/run/extmethod-overload.scala +++ b/tests/run/extmethod-overload.scala @@ -22,13 +22,13 @@ object Test extends App { // Test with extension methods in given object object test1 { - given Foo as AnyRef { + extension Foo: def (x: Int) |+| (y: Int) = x + y def (x: Int) |+| (y: String) = x + y.length def [T](xs: List[T]) +++ (ys: List[T]): List[T] = xs ++ ys ++ ys def [T](xs: List[T]) +++ (ys: Iterator[T]): List[T] = xs ++ ys ++ ys - } + end Foo assert((1 |+| 2) == 3) assert((1 |+| "2") == 2) diff --git a/tests/run/instances.scala b/tests/run/instances.scala index 471b603d3ce9..c8533fad9225 100644 --- a/tests/run/instances.scala +++ b/tests/run/instances.scala @@ -31,10 +31,8 @@ object Test extends App { extension listListOps on [T](xs: List[List[T]]): def flattened = xs.foldLeft[List[T]](Nil)(_ ++ _) - // A right associative op. Note: can't use given extension for this! - given prepend as AnyRef { + extension prepend: def [T](x: T) :: (xs: Seq[T]) = x +: xs - } val ss: Seq[Int] = List(1, 2, 3) val ss1 = 0 :: ss