diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 5326361ada98..2cfd292fd6dd 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -915,16 +915,16 @@ object desugar { name = normalizeName(mdef, mdef.tpt).asTermName, paramss = if mdef.name.isRightAssocOperatorName then - val (typaramss, paramss) = mdef.paramss.span(isTypeParamClause) // first extract type parameters + val (rightTyParams, paramss) = mdef.paramss.span(isTypeParamClause) // first extract type parameters paramss match - case params :: paramss1 => // `params` must have a single parameter and without `given` flag + case rightParam :: paramss1 => // `rightParam` must have a single parameter and without `given` flag def badRightAssoc(problem: String) = report.error(em"right-associative extension method $problem", mdef.srcPos) extParamss ++ mdef.paramss - params match + rightParam match case ValDefs(vparam :: Nil) => if !vparam.mods.is(Given) then // we merge the extension parameters with the method parameters, @@ -934,8 +934,10 @@ object desugar { // def %:[E](f: F)(g: G)(using H): Res = ??? // will be encoded as // def %:[A](using B)[E](f: F)(c: C)(using D)(g: G)(using H): Res = ??? - val (leadingUsing, otherExtParamss) = extParamss.span(isUsingOrTypeParamClause) - leadingUsing ::: typaramss ::: params :: otherExtParamss ::: paramss1 + // + // If you change the names of the clauses below, also change them in right-associative-extension-methods.md + val (leftTyParamsAndLeadingUsing, leftParamAndTrailingUsing) = extParamss.span(isUsingOrTypeParamClause) + leftTyParamsAndLeadingUsing ::: rightTyParams ::: rightParam :: leftParamAndTrailingUsing ::: paramss1 else badRightAssoc("cannot start with using clause") case _ => diff --git a/compiler/src/dotty/tools/dotc/config/Feature.scala b/compiler/src/dotty/tools/dotc/config/Feature.scala index 188526bb094f..419ed5868cbf 100644 --- a/compiler/src/dotty/tools/dotc/config/Feature.scala +++ b/compiler/src/dotty/tools/dotc/config/Feature.scala @@ -28,6 +28,7 @@ object Feature: val symbolLiterals = deprecated("symbolLiterals") val fewerBraces = experimental("fewerBraces") val saferExceptions = experimental("saferExceptions") + val clauseInterleaving = experimental("clauseInterleaving") val pureFunctions = experimental("pureFunctions") val captureChecking = experimental("captureChecking") val into = experimental("into") @@ -76,6 +77,8 @@ object Feature: def namedTypeArgsEnabled(using Context) = enabled(namedTypeArguments) + def clauseInterleavingEnabled(using Context) = enabled(clauseInterleaving) + def genericNumberLiteralsEnabled(using Context) = enabled(genericNumberLiterals) def scala2ExperimentalMacroEnabled(using Context) = enabled(scala2macros) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index ff5e95f3aa03..479ae1fa9095 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3079,6 +3079,42 @@ object Parsers { /* -------- PARAMETERS ------------------------------------------- */ + /** DefParamClauses ::= DefParamClause { DefParamClause } -- and two DefTypeParamClause cannot be adjacent + * DefParamClause ::= DefTypeParamClause + * | DefTermParamClause + * | UsingParamClause + */ + def typeOrTermParamClauses( + ownerKind: ParamOwner, + numLeadParams: Int = 0 + ): List[List[TypeDef] | List[ValDef]] = + + def recur(firstClause: Boolean, numLeadParams: Int, prevIsTypeClause: Boolean): List[List[TypeDef] | List[ValDef]] = + newLineOptWhenFollowedBy(LPAREN) + newLineOptWhenFollowedBy(LBRACKET) + if in.token == LPAREN then + val paramsStart = in.offset + val params = termParamClause( + numLeadParams, + firstClause = firstClause) + val lastClause = params.nonEmpty && params.head.mods.flags.is(Implicit) + params :: ( + if lastClause then Nil + else recur(firstClause = false, numLeadParams + params.length, prevIsTypeClause = false)) + else if in.token == LBRACKET then + if prevIsTypeClause then + syntaxError( + em"Type parameter lists must be separated by a term or using parameter list", + in.offset + ) + typeParamClause(ownerKind) :: recur(firstClause, numLeadParams, prevIsTypeClause = true) + else Nil + end recur + + recur(firstClause = true, numLeadParams = numLeadParams, prevIsTypeClause = false) + end typeOrTermParamClauses + + /** ClsTypeParamClause::= ‘[’ ClsTypeParam {‘,’ ClsTypeParam} ‘]’ * ClsTypeParam ::= {Annotation} [‘+’ | ‘-’] * id [HkTypeParamClause] TypeParamBounds @@ -3132,34 +3168,39 @@ object Parsers { /** ContextTypes ::= FunArgType {‘,’ FunArgType} */ - def contextTypes(ofClass: Boolean, nparams: Int, impliedMods: Modifiers): List[ValDef] = + def contextTypes(ofClass: Boolean, numLeadParams: Int, impliedMods: Modifiers): List[ValDef] = val tps = commaSeparated(funArgType) - var counter = nparams + var counter = numLeadParams def nextIdx = { counter += 1; counter } val paramFlags = if ofClass then LocalParamAccessor else Param tps.map(makeSyntheticParameter(nextIdx, _, paramFlags | Synthetic | impliedMods.flags)) - /** ClsParamClause ::= ‘(’ [‘erased’] ClsParams ‘)’ | UsingClsParamClause - * UsingClsParamClause::= ‘(’ ‘using’ [‘erased’] (ClsParams | ContextTypes) ‘)’ + /** ClsTermParamClause ::= ‘(’ [‘erased’] ClsParams ‘)’ | UsingClsTermParamClause + * UsingClsTermParamClause::= ‘(’ ‘using’ [‘erased’] (ClsParams | ContextTypes) ‘)’ * ClsParams ::= ClsParam {‘,’ ClsParam} * ClsParam ::= {Annotation} + * + * TypelessClause ::= DefTermParamClause + * | UsingParamClause * - * DefParamClause ::= ‘(’ [‘erased’] DefParams ‘)’ | UsingParamClause - * UsingParamClause ::= ‘(’ ‘using’ [‘erased’] (DefParams | ContextTypes) ‘)’ - * DefParams ::= DefParam {‘,’ DefParam} - * DefParam ::= {Annotation} [‘inline’] Param + * DefTermParamClause::= [nl] ‘(’ [DefTermParams] ‘)’ + * UsingParamClause ::= ‘(’ ‘using’ [‘erased’] (DefTermParams | ContextTypes) ‘)’ + * DefImplicitClause ::= [nl] ‘(’ ‘implicit’ DefTermParams ‘)’ + * DefTermParams ::= DefTermParam {‘,’ DefTermParam} + * DefTermParam ::= {Annotation} [‘inline’] Param * * Param ::= id `:' ParamType [`=' Expr] * * @return the list of parameter definitions */ - def paramClause(nparams: Int, // number of parameters preceding this clause - ofClass: Boolean = false, // owner is a class - ofCaseClass: Boolean = false, // owner is a case class - prefix: Boolean = false, // clause precedes name of an extension method - givenOnly: Boolean = false, // only given parameters allowed - firstClause: Boolean = false // clause is the first in regular list of clauses - ): List[ValDef] = { + def termParamClause( + numLeadParams: Int, // number of parameters preceding this clause + ofClass: Boolean = false, // owner is a class + ofCaseClass: Boolean = false, // owner is a case class + prefix: Boolean = false, // clause precedes name of an extension method + givenOnly: Boolean = false, // only given parameters allowed + firstClause: Boolean = false // clause is the first in regular list of clauses + ): List[ValDef] = { var impliedMods: Modifiers = EmptyModifiers def addParamMod(mod: () => Mod) = impliedMods = addMod(impliedMods, atSpan(in.skipToken()) { mod() }) @@ -3224,7 +3265,7 @@ object Parsers { checkVarArgsRules(rest) } - // begin paramClause + // begin termParamClause inParens { if in.token == RPAREN && !prefix && !impliedMods.is(Given) then Nil else @@ -3239,28 +3280,30 @@ object Parsers { || startParamTokens.contains(in.token) || isIdent && (in.name == nme.inline || in.lookahead.isColon) if isParams then commaSeparated(() => param()) - else contextTypes(ofClass, nparams, impliedMods) + else contextTypes(ofClass, numLeadParams, impliedMods) checkVarArgsRules(clause) clause } } - /** ClsParamClauses ::= {ClsParamClause} [[nl] ‘(’ [‘implicit’] ClsParams ‘)’] - * DefParamClauses ::= {DefParamClause} [[nl] ‘(’ [‘implicit’] DefParams ‘)’] + /** ClsTermParamClauses ::= {ClsTermParamClause} [[nl] ‘(’ [‘implicit’] ClsParams ‘)’] + * TypelessClauses ::= TypelessClause {TypelessClause} * * @return The parameter definitions */ - def paramClauses(ofClass: Boolean = false, - ofCaseClass: Boolean = false, - givenOnly: Boolean = false, - numLeadParams: Int = 0): List[List[ValDef]] = + def termParamClauses( + ofClass: Boolean = false, + ofCaseClass: Boolean = false, + givenOnly: Boolean = false, + numLeadParams: Int = 0 + ): List[List[ValDef]] = - def recur(firstClause: Boolean, nparams: Int): List[List[ValDef]] = + def recur(firstClause: Boolean, numLeadParams: Int): List[List[ValDef]] = newLineOptWhenFollowedBy(LPAREN) if in.token == LPAREN then val paramsStart = in.offset - val params = paramClause( - nparams, + val params = termParamClause( + numLeadParams, ofClass = ofClass, ofCaseClass = ofCaseClass, givenOnly = givenOnly, @@ -3268,12 +3311,12 @@ object Parsers { val lastClause = params.nonEmpty && params.head.mods.flags.is(Implicit) params :: ( if lastClause then Nil - else recur(firstClause = false, nparams + params.length)) + else recur(firstClause = false, numLeadParams + params.length)) else Nil end recur recur(firstClause = true, numLeadParams) - end paramClauses + end termParamClauses /* -------- DEFS ------------------------------------------- */ @@ -3514,11 +3557,15 @@ object Parsers { } } + + /** DefDef ::= DefSig [‘:’ Type] ‘=’ Expr - * | this ParamClause ParamClauses `=' ConstrExpr + * | this TypelessClauses [DefImplicitClause] `=' ConstrExpr * DefDcl ::= DefSig `:' Type - * DefSig ::= id [DefTypeParamClause] DefParamClauses - * | ExtParamClause [nl] [‘.’] id DefParamClauses + * DefSig ::= id [DefTypeParamClause] DefTermParamClauses + * + * if clauseInterleaving is enabled: + * DefSig ::= id [DefParamClauses] [DefImplicitClause] */ def defDefOrDcl(start: Offset, mods: Modifiers, numLeadParams: Int = 0): DefDef = atSpan(start, nameStart) { @@ -3537,7 +3584,7 @@ object Parsers { if (in.token == THIS) { in.nextToken() - val vparamss = paramClauses(numLeadParams = numLeadParams) + val vparamss = termParamClauses(numLeadParams = numLeadParams) if (vparamss.isEmpty || vparamss.head.take(1).exists(_.mods.isOneOf(GivenOrImplicit))) in.token match { case LBRACKET => syntaxError(em"no type parameters allowed here") @@ -3555,9 +3602,18 @@ object Parsers { val mods1 = addFlag(mods, Method) val ident = termIdent() var name = ident.name.asTermName - val tparams = typeParamClauseOpt(ParamOwner.Def) - val vparamss = paramClauses(numLeadParams = numLeadParams) + val paramss = + if in.featureEnabled(Feature.clauseInterleaving) then + // If you are making interleaving stable manually, please refer to the PR introducing it instead, section "How to make non-experimental" + typeOrTermParamClauses(ParamOwner.Def, numLeadParams = numLeadParams) + else + val tparams = typeParamClauseOpt(ParamOwner.Def) + val vparamss = termParamClauses(numLeadParams = numLeadParams) + + joinParams(tparams, vparamss) + var tpt = fromWithinReturnType { typedOpt() } + if (migrateTo3) newLineOptWhenFollowedBy(LBRACE) val rhs = if in.token == EQUALS then @@ -3574,7 +3630,7 @@ object Parsers { accept(EQUALS) expr() - val ddef = DefDef(name, joinParams(tparams, vparamss), tpt, rhs) + val ddef = DefDef(name, paramss, tpt, rhs) if (isBackquoted(ident)) ddef.pushAttachment(Backquoted, ()) finalizeDef(ddef, mods1, start) } @@ -3695,12 +3751,12 @@ object Parsers { val templ = templateOpt(constr) finalizeDef(TypeDef(name, templ), mods, start) - /** ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses + /** ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsTermParamClauses */ def classConstr(isCaseClass: Boolean = false): DefDef = atSpan(in.lastOffset) { val tparams = typeParamClauseOpt(ParamOwner.Class) val cmods = fromWithinClassConstr(constrModsOpt()) - val vparamss = paramClauses(ofClass = true, ofCaseClass = isCaseClass) + val vparamss = termParamClauses(ofClass = true, ofCaseClass = isCaseClass) makeConstructor(tparams, vparamss).withMods(cmods) } @@ -3802,7 +3858,7 @@ object Parsers { newLineOpt() val vparamss = if in.token == LPAREN && in.lookahead.isIdent(nme.using) - then paramClauses(givenOnly = true) + then termParamClauses(givenOnly = true) else Nil newLinesOpt() val noParams = tparams.isEmpty && vparamss.isEmpty @@ -3837,20 +3893,20 @@ object Parsers { finalizeDef(gdef, mods1, start) } - /** Extension ::= ‘extension’ [DefTypeParamClause] {UsingParamClause} ‘(’ DefParam ‘)’ + /** Extension ::= ‘extension’ [DefTypeParamClause] {UsingParamClause} ‘(’ DefTermParam ‘)’ * {UsingParamClause} ExtMethods */ def extension(): ExtMethods = val start = in.skipToken() val tparams = typeParamClauseOpt(ParamOwner.Def) val leadParamss = ListBuffer[List[ValDef]]() - def nparams = leadParamss.map(_.length).sum + def numLeadParams = leadParamss.map(_.length).sum while - val extParams = paramClause(nparams, prefix = true) + val extParams = termParamClause(numLeadParams, prefix = true) leadParamss += extParams isUsingClause(extParams) do () - leadParamss ++= paramClauses(givenOnly = true, numLeadParams = nparams) + leadParamss ++= termParamClauses(givenOnly = true, numLeadParams = numLeadParams) if in.isColon then syntaxError(em"no `:` expected here") in.nextToken() @@ -3858,11 +3914,11 @@ object Parsers { if in.token == EXPORT then exportClause() else if isDefIntro(modifierTokens) then - extMethod(nparams) :: Nil + extMethod(numLeadParams) :: Nil else in.observeIndented() newLineOptWhenFollowedBy(LBRACE) - if in.isNestedStart then inDefScopeBraces(extMethods(nparams)) + if in.isNestedStart then inDefScopeBraces(extMethods(numLeadParams)) else { syntaxErrorOrIncomplete(em"Extension without extension methods") ; Nil } val result = atSpan(start)(ExtMethods(joinParams(tparams, leadParamss.toList), methods)) val comment = in.getDocComment(start) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index f94ff68d0698..6f258fdddbe9 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -896,30 +896,31 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { if isExtension then val paramss = if tree.name.isRightAssocOperatorName then + // If you change the names of the clauses below, also change them in right-associative-extension-methods.md // we have the following encoding of tree.paramss: - // (leadingTyParamss ++ leadingUsing - // ++ rightTyParamss ++ rightParamss - // ++ leftParamss ++ trailingUsing ++ rest) + // (leftTyParams ++ leadingUsing + // ++ rightTyParams ++ rightParam + // ++ leftParam ++ trailingUsing ++ rest) // e.g. // extension [A](using B)(c: C)(using D) // def %:[E](f: F)(g: G)(using H): Res = ??? // will have the following values: - // - leadingTyParamss = List(`[A]`) + // - leftTyParams = List(`[A]`) // - leadingUsing = List(`(using B)`) - // - rightTyParamss = List(`[E]`) - // - rightParamss = List(`(f: F)`) - // - leftParamss = List(`(c: C)`) + // - rightTyParams = List(`[E]`) + // - rightParam = List(`(f: F)`) + // - leftParam = List(`(c: C)`) // - trailingUsing = List(`(using D)`) // - rest = List(`(g: G)`, `(using H)`) - // we need to swap (rightTyParams ++ rightParamss) with (leftParamss ++ trailingUsing) - val (leadingTyParamss, rest1) = tree.paramss.span(isTypeParamClause) + // we need to swap (rightTyParams ++ rightParam) with (leftParam ++ trailingUsing) + val (leftTyParams, rest1) = tree.paramss.span(isTypeParamClause) val (leadingUsing, rest2) = rest1.span(isUsingClause) - val (rightTyParamss, rest3) = rest2.span(isTypeParamClause) - val (rightParamss, rest4) = rest3.splitAt(1) - val (leftParamss, rest5) = rest4.splitAt(1) + val (rightTyParams, rest3) = rest2.span(isTypeParamClause) + val (rightParam, rest4) = rest3.splitAt(1) + val (leftParam, rest5) = rest4.splitAt(1) val (trailingUsing, rest6) = rest5.span(isUsingClause) - if leftParamss.nonEmpty then - leadingTyParamss ::: leadingUsing ::: leftParamss ::: trailingUsing ::: rightTyParamss ::: rightParamss ::: rest6 + if leftParam.nonEmpty then + leftTyParams ::: leadingUsing ::: leftParam ::: trailingUsing ::: rightTyParams ::: rightParam ::: rest6 else tree.paramss // it wasn't a binary operator, after all. else diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index cd33fe9cef24..e15121ff1636 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -444,10 +444,17 @@ trait Applications extends Compatibility { /** The function's type after widening and instantiating polytypes * with TypeParamRefs in constraint set */ - @threadUnsafe lazy val methType: Type = liftedFunType.widen match { - case funType: MethodType => funType - case funType: PolyType => instantiateWithTypeVars(funType) - case tp => tp //was: funType + @threadUnsafe lazy val methType: Type = { + def rec(t: Type): Type = { + t.widen match{ + case funType: MethodType => funType + case funType: PolyType => + rec(instantiateWithTypeVars(funType)) + case tp => tp + } + } + + rec(liftedFunType) } @threadUnsafe lazy val liftedFunType: Type = @@ -1144,8 +1151,12 @@ trait Applications extends Compatibility { val typedArgs = if (isNamed) typedNamedArgs(tree.args) else tree.args.mapconserve(typedType(_)) record("typedTypeApply") typedExpr(tree.fun, PolyProto(typedArgs, pt)) match { - case _: TypeApply if !ctx.isAfterTyper => - errorTree(tree, em"illegal repeated type application") + case fun: TypeApply if !ctx.isAfterTyper => + val function = fun.fun + val args = (fun.args ++ tree.args).map(_.show).mkString(", ") + errorTree(tree, em"""illegal repeated type application + |You might have meant something like: + |${function}[${args}]""") case typedFn => typedFn.tpe.widen match { case pt: PolyType => diff --git a/docs/_docs/internals/syntax.md b/docs/_docs/internals/syntax.md index 76664569bb17..1fbb7a34b078 100644 --- a/docs/_docs/internals/syntax.md +++ b/docs/_docs/internals/syntax.md @@ -347,9 +347,6 @@ ClsTypeParamClause::= ‘[’ ClsTypeParam {‘,’ ClsTypeParam} ‘]’ ClsTypeParam ::= {Annotation} [‘+’ | ‘-’] TypeDef(Modifiers, name, tparams, bounds) id [HkTypeParamClause] TypeParamBounds Bound(below, above, context) -DefTypeParamClause::= ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’ -DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeParamBounds - TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’ TypTypeParam ::= {Annotation} id [HkTypeParamClause] TypeBounds @@ -363,13 +360,24 @@ ClsParamClause ::= [nl] ‘(’ ClsParams ‘)’ ClsParams ::= ClsParam {‘,’ ClsParam} ClsParam ::= {Annotation} ValDef(mods, id, tpe, expr) -- point of mods on val/var [{Modifier} (‘val’ | ‘var’) | ‘inline’] Param -Param ::= id ‘:’ ParamType [‘=’ Expr] -DefParamClauses ::= {DefParamClause} [[nl] ‘(’ [‘implicit’] DefParams ‘)’] -DefParamClause ::= [nl] ‘(’ DefParams ‘)’ | UsingParamClause -UsingParamClause ::= [nl] ‘(’ ‘using’ (DefParams | FunArgTypes) ‘)’ -DefParams ::= DefParam {‘,’ DefParam} -DefParam ::= {Annotation} [‘inline’] Param ValDef(mods, id, tpe, expr) -- point of mods at id. +DefParamClauses ::= DefParamClause { DefParamClause } -- and two DefTypeParamClause cannot be adjacent +DefParamClause ::= DefTypeParamClause + | DefTermParamClause + | UsingParamClause +TypelessClauses ::= TypelessClause {TypelessClause} +TypelessClause ::= DefTermParamClause + | UsingParamClause + +DefTypeParamClause::= [nl] ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’ +DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeParamBounds +DefTermParamClause::= [nl] ‘(’ [DefTermParams] ‘)’ +UsingParamClause ::= [nl] ‘(’ ‘using’ (DefTermParams | FunArgTypes) ‘)’ +DefImplicitClause ::= [nl] ‘(’ ‘implicit’ DefTermParams ‘)’ + +DefTermParams ::= DefTermParam {‘,’ DefTermParam} +DefTermParam ::= {Annotation} [‘inline’] Param ValDef(mods, id, tpe, expr) -- point of mods at id. +Param ::= id ‘:’ ParamType [‘=’ Expr] ``` ### Bindings and Imports @@ -419,8 +427,8 @@ Dcl ::= RefineDcl | ‘var’ VarDcl ValDcl ::= ids ‘:’ Type PatDef(_, ids, tpe, EmptyTree) VarDcl ::= ids ‘:’ Type PatDef(_, ids, tpe, EmptyTree) -DefDcl ::= DefSig ‘:’ Type DefDef(_, name, tparams, vparamss, tpe, EmptyTree) -DefSig ::= id [DefTypeParamClause] DefParamClauses +DefDcl ::= DefSig ‘:’ Type DefDef(_, name, paramss, tpe, EmptyTree) +DefSig ::= id [DefParamClauses] [DefImplicitClause] TypeDcl ::= id [TypeParamClause] {FunParamClause} TypeBounds TypeDefTree(_, name, tparams, bound [‘=’ Type] @@ -431,8 +439,8 @@ Def ::= ‘val’ PatDef | TmplDef PatDef ::= ids [‘:’ Type] ‘=’ Expr | Pattern2 [‘:’ Type] ‘=’ Expr PatDef(_, pats, tpe?, expr) -DefDef ::= DefSig [‘:’ Type] ‘=’ Expr DefDef(_, name, tparams, vparamss, tpe, expr) - | ‘this’ DefParamClause DefParamClauses ‘=’ ConstrExpr DefDef(_, , Nil, vparamss, EmptyTree, expr | Block) +DefDef ::= DefSig [‘:’ Type] ‘=’ Expr DefDef(_, name, paramss, tpe, expr) + | ‘this’ TypelessClauses [DefImplicitClause] ‘=’ ConstrExpr DefDef(_, , vparamss, EmptyTree, expr | Block) TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef | [‘case’] ‘object’ ObjectDef @@ -444,10 +452,10 @@ ConstrMods ::= {Annotation} [AccessModifier] ObjectDef ::= id [Template] ModuleDef(mods, name, template) // no constructor EnumDef ::= id ClassConstr InheritClauses EnumBody GivenDef ::= [GivenSig] (AnnotType [‘=’ Expr] | StructuralInstance) -GivenSig ::= [id] [DefTypeParamClause] {UsingParamClause} ‘:’ -- one of `id`, `DefParamClause`, `UsingParamClause` must be present +GivenSig ::= [id] [DefTypeParamClause] {UsingParamClause} ‘:’ -- one of `id`, `DefTypeParamClause`, `UsingParamClause` must be present StructuralInstance ::= ConstrApp {‘with’ ConstrApp} [‘with’ WithTemplateBody] Extension ::= ‘extension’ [DefTypeParamClause] {UsingParamClause} - ‘(’ DefParam ‘)’ {UsingParamClause} ExtMethods + ‘(’ DefTermParam ‘)’ {UsingParamClause} ExtMethods ExtMethods ::= ExtMethod | [nl] <<< ExtMethod {semi ExtMethod} >>> ExtMethod ::= {Annotation [nl]} {Modifier} ‘def’ DefDef | Export diff --git a/docs/_docs/reference/contextual/right-associative-extension-methods.md b/docs/_docs/reference/contextual/right-associative-extension-methods.md index 068123df8cd2..61f0beece6ed 100644 --- a/docs/_docs/reference/contextual/right-associative-extension-methods.md +++ b/docs/_docs/reference/contextual/right-associative-extension-methods.md @@ -4,45 +4,57 @@ title: "Right-Associative Extension Methods: Details" nightlyOf: https://docs.scala-lang.org/scala3/reference/contextual/right-associative-extension-methods.html --- -The most general form of leading parameters of an extension method is as follows: + +The most general signature an extension method can have is as follows: + - An optional type clause `leftTyParams` - A possibly empty list of using clauses `leadingUsing` - - A single parameter `extensionParam` + - A single parameter `leftParam` (in an explicit term clause) - A possibly empty list of using clauses `trailingUsing` + - A name (preceded by the `def` keyword) + - An optional type clause `rightTyParams` + - An optional single parameter `rightParam` (in an explicit term clause) + - Any number of any clauses `rest` -This is then followed by `def`, the method name, and possibly further parameters -`otherParams`. An example is: +For example: ```scala - extension (using a: A, b: B)(using c: C) // <-- leadingUsing - (x: X) // <-- extensionParam + extension [T] // <-- leftTyParams + (using a: A, b: B)(using c: C) // <-- leadingUsing + (x: X) // <-- leftParam (using d: D) // <-- trailingUsing - def +:: (y: Y)(using e: E)(z: Z) // <-- otherParams + def +:: [U] // <-- rightTyParams + (y: Y) // <-- rightParam + (using e: E)(z: Z) // <-- rest ``` + An extension method is treated as a right-associative operator (as in [SLS §6.12.3](https://www.scala-lang.org/files/archive/spec/2.13/06-expressions.html#infix-operations)) -if it has a name ending in `:` and is immediately followed by a -single parameter. In the example above, that parameter is `(y: Y)`. +if it has a name ending in `:`, and is immediately followed by a +single explicit term parameter (in other words, `rightParam` is present). In the example above, that parameter is `(y: Y)`. The Scala compiler pre-processes a right-associative infix operation such as `x +: xs` to `xs.+:(x)` if `x` is a pure expression or a call-by-name parameter and to `val y = x; xs.+:(y)` otherwise. This is necessary since a regular right-associative infix method is defined in the class of its right operand. To make up for this swap, -the expansion of right-associative extension methods performs an analogous parameter swap. More precisely, if `otherParams` consists of a single parameter -`rightParam` followed by `remaining`, the total parameter sequence +the expansion of right-associative extension methods performs the inverse parameter swap. More precisely, if `rightParam` is present, the total parameter sequence of the extension method's expansion is: ``` - leadingUsing rightParam trailingUsing extensionParam remaining + leftTyParams leadingUsing rightTyParams rightParam leftParam trailingUsing rest ``` +In other words, we swap `leftParams trailingUsing` with `rightTyParam rightParam`. + For instance, the `+::` method above would become ```scala - def +:: (using a: A, b: B)(using c: C) + def +:: [T] + (using a: A, b: B)(using c: C) + [U] (y: Y) - (using d: D) (x: X) + (using d: D) (using e: E)(z: Z) ``` diff --git a/docs/_docs/reference/contextual/using-clauses.md b/docs/_docs/reference/contextual/using-clauses.md index 8f410ebc026e..61b80ee27d91 100644 --- a/docs/_docs/reference/contextual/using-clauses.md +++ b/docs/_docs/reference/contextual/using-clauses.md @@ -153,8 +153,8 @@ Here is the new syntax of parameters and arguments seen as a delta from the [sta ``` ClsParamClause ::= ... | UsingClsParamClause -DefParamClauses ::= ... | UsingParamClause +DefParamClause ::= ... | UsingParamClause UsingClsParamClause ::= ‘(’ ‘using’ (ClsParams | Types) ‘)’ -UsingParamClause ::= ‘(’ ‘using’ (DefParams | Types) ‘)’ +UsingParamClause ::= ‘(’ ‘using’ (DefTermParams | Types) ‘)’ ParArgumentExprs ::= ... | ‘(’ ‘using’ ExprsInParens ‘)’ ``` diff --git a/docs/_docs/reference/experimental/generalized-method-syntax.md b/docs/_docs/reference/experimental/generalized-method-syntax.md new file mode 100644 index 000000000000..072052c1ae10 --- /dev/null +++ b/docs/_docs/reference/experimental/generalized-method-syntax.md @@ -0,0 +1,102 @@ +--- +layout: doc-page +title: "Generalized Method Syntax" +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/generalized-method-syntax.html +--- + +This feature is not yet part of the Scala 3 language definition. It can be made available by a language import: + +```scala +import scala.language.experimental.clauseInterleaving +``` + +The inclusion of using clauses is not the only way in which methods have been updated, type parameter clauses are now allowed in any number and at any position. + +## Syntax Changes + +### In Scala 2 + +The old syntax only allowed zero or one type parameter clause, followed by any number of term clauses, optionnally followed by an implicit clause: + +```scala +def foo[T, U](x: T)(y: U)(z: Int, s: String)(a: Array[T])(implicit ordInt: Ord[Int], l: List[U]) +``` + +### In Scala 3 + +The new syntax allows any number of type clauses, as long as they are not adjacent: +(do note however that [implicit clause are discouraged, in favor of using clauses](https://docs.scala-lang.org/scala3/reference/contextual/relationship-implicits.html)) + +```scala +def foo[T, U](x: T)(y: U)[V](z: V, s: String)(using Ord[Int])[A](a: Array[A])(implicit List[U]) +``` + +### Unchanged + +Class definitions and type declarations are unaffected, there can only be up to one type clause, in leading posion. + +## Motivation + +The new syntax is a powerful but natural extension of the old one, it allows new design patterns while staying intuitive and legible. + +### Dependent Type Clauses + +As type clauses can come after term clauses, it is now possible to have type parameters that depend on term parameters: + +```scala +trait Key { type Value } +trait DB { + def get(k: Key): Option[k.Value] // dependent result type + def getOrElse(k: Key)[V >: k.Value](default: V): V // dependent type parameter +} +``` + +Note that simply replacing `V` by `k.Value` would not be equivalent. For example, if `k.Value` is `Some[Int]`, only the above allows: +`getOrElse(k)[Option[Int]](None)`, which returns a `Number`. + +## Details + +### Application + +Method application is unchanged. +When multiple type clauses are expected but not all are passed, the rightmost ones are inferred. + +In particular, the following does not type check, even though the argument `Char` is only valid for `C`: +```scala +def triple[I <: Int](using Ordering[I])[C <: Char](a: I, b: C) = ??? +triple[Char](0, 'c') // error: Char does not conform to upperbound Int +``` + +### Extension Methods + +Extension methods follow the same syntax, for example the following is valid: +```scala +extension [T](l1: List[T]) + def zipWith[U](l2: List[U])[V](l3: List[V]): List[(T,U,V)] +``` + +### When to use + +We recommand to always put a unique type clause at the beginning, unless it is not possible to do so. +For example, the extension method `zipWith` above should be written `zipWith[U, V](l2: List[U], l3: List[V]): List[(T,U,V)]` instead. +On the other hand, the `getOrElse` method is recommended as-is, as it cannot be written with a leading type clause. + +### Formal syntax + +``` +DefDcl ::= DefSig ‘:’ Type +DefDef ::= DefSig [‘:’ Type] ‘=’ Expr +DefSig ::= id [DefParamClauses] [DefImplicitClause] +DefParamClauses ::= DefParamClause { DefParamClause } -- and two DefTypeParamClause cannot be adjacent +DefParamClause ::= DefTypeParamClause + | DefTermParamClause + | UsingParamClause +DefTypeParamClause::= [nl] ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’ +DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeParamBounds +DefTermParamClause::= [nl] ‘(’ [DefTermParams] ‘)’ +UsingParamClause ::= [nl] ‘(’ ‘using’ (DefTermParams | FunArgTypes) ‘)’ +DefImplicitClause ::= [nl] ‘(’ ‘implicit’ DefTermParams ‘)’ +DefTermParams ::= DefTermParam {‘,’ DefTermParam} +DefTermParam ::= {Annotation} [‘inline’] Param +Param ::= id ‘:’ ParamType [‘=’ Expr] +``` diff --git a/docs/_docs/reference/syntax.md b/docs/_docs/reference/syntax.md index 7e4b81b1ef5a..8da41c7e6d0c 100644 --- a/docs/_docs/reference/syntax.md +++ b/docs/_docs/reference/syntax.md @@ -338,9 +338,6 @@ ArgumentPatterns ::= ‘(’ [Patterns] ‘)’ ClsTypeParamClause::= ‘[’ ClsTypeParam {‘,’ ClsTypeParam} ‘]’ ClsTypeParam ::= {Annotation} [‘+’ | ‘-’] id [HkTypeParamClause] TypeParamBounds -DefTypeParamClause::= ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’ -DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeParamBounds - TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’ TypTypeParam ::= {Annotation} id [HkTypeParamClause] TypeBounds @@ -352,13 +349,20 @@ ClsParamClause ::= [nl] ‘(’ ClsParams ‘)’ | [nl] ‘(’ ‘using’ (ClsParams | FunArgTypes) ‘)’ ClsParams ::= ClsParam {‘,’ ClsParam} ClsParam ::= {Annotation} [{Modifier} (‘val’ | ‘var’) | ‘inline’] Param -Param ::= id ‘:’ ParamType [‘=’ Expr] -DefParamClauses ::= {DefParamClause} [[nl] ‘(’ [‘implicit’] DefParams ‘)’] -DefParamClause ::= [nl] ‘(’ DefParams ‘)’ | UsingParamClause -UsingParamClause ::= [nl] ‘(’ ‘using’ (DefParams | FunArgTypes) ‘)’ -DefParams ::= DefParam {‘,’ DefParam} -DefParam ::= {Annotation} [‘inline’] Param +TypelessClauses ::= TypelessClause {TypelessClause} +TypelessClause ::= DefTermParamClause + | UsingParamClause + +DefTypeParamClause::= [nl] ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’ +DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeParamBounds +DefTermParamClause::= [nl] ‘(’ [DefTermParams] ‘)’ +UsingParamClause ::= [nl] ‘(’ ‘using’ (DefTermParams | FunArgTypes) ‘)’ +DefImplicitClause ::= [nl] ‘(’ ‘implicit’ DefTermParams ‘)’ + +DefTermParams ::= DefTermParam {‘,’ DefTermParam} +DefTermParam ::= {Annotation} [‘inline’] Param +Param ::= id ‘:’ ParamType [‘=’ Expr] ``` ### Bindings and Imports @@ -409,8 +413,8 @@ Dcl ::= RefineDcl ValDcl ::= ids ‘:’ Type VarDcl ::= ids ‘:’ Type DefDcl ::= DefSig ‘:’ Type -DefSig ::= id [DefTypeParamClause] DefParamClauses -TypeDcl ::= id [TypeParamClause] {FunParamClause} TypeBounds [‘=’ Type] +DefSig ::= id [DefTypeParamClause] [TypelessClauses] [DefImplicitClause] +TypeDcl ::= id [TypeParamClause] {FunParamClause} TypeBounds Def ::= ‘val’ PatDef | ‘var’ PatDef @@ -420,7 +424,7 @@ Def ::= ‘val’ PatDef PatDef ::= ids [‘:’ Type] ‘=’ Expr | Pattern2 [‘:’ Type] ‘=’ Expr DefDef ::= DefSig [‘:’ Type] ‘=’ Expr - | ‘this’ DefParamClause DefParamClauses ‘=’ ConstrExpr + | ‘this’ TypelessClauses [DefImplicitClause] ‘=’ ConstrExpr TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef | [‘case’] ‘object’ ObjectDef @@ -432,10 +436,10 @@ ConstrMods ::= {Annotation} [AccessModifier] ObjectDef ::= id [Template] EnumDef ::= id ClassConstr InheritClauses EnumBody GivenDef ::= [GivenSig] (AnnotType [‘=’ Expr] | StructuralInstance) -GivenSig ::= [id] [DefTypeParamClause] {UsingParamClause} ‘:’ -- one of `id`, `DefParamClause`, `UsingParamClause` must be present +GivenSig ::= [id] [DefTypeParamClause] {UsingParamClause} ‘:’ -- one of `id`, `DefTypeParamClause`, `UsingParamClause` must be present StructuralInstance ::= ConstrApp {‘with’ ConstrApp} [‘with’ WithTemplateBody] Extension ::= ‘extension’ [DefTypeParamClause] {UsingParamClause} - ‘(’ DefParam ‘)’ {UsingParamClause} ExtMethods + ‘(’ DefTermParam ‘)’ {UsingParamClause} ExtMethods ExtMethods ::= ExtMethod | [nl] <<< ExtMethod {semi ExtMethod} >>> ExtMethod ::= {Annotation [nl]} {Modifier} ‘def’ DefDef | Export diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 48a387e64169..22ec107aeae8 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -3143,7 +3143,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Extension methods of `MethodType` */ trait MethodTypeMethods: extension (self: MethodType) - /** Is this the type of given parameter clause `(implicit X1, ..., Xn)`, `(given X1, ..., Xn)` or `(given x1: X1, ..., xn: Xn)` */ + /** Is this the type of using parameter clause `(implicit X1, ..., Xn)`, `(using X1, ..., Xn)` or `(using x1: X1, ..., xn: Xn)` */ def isImplicit: Boolean def isErased: Boolean def param(idx: Int): TypeRepr diff --git a/library/src/scala/runtime/stdLibPatches/language.scala b/library/src/scala/runtime/stdLibPatches/language.scala index 401926dbab4d..d92495c6f5aa 100644 --- a/library/src/scala/runtime/stdLibPatches/language.scala +++ b/library/src/scala/runtime/stdLibPatches/language.scala @@ -61,6 +61,14 @@ object language: @compileTimeOnly("`saferExceptions` can only be used at compile time in import statements") object saferExceptions + /** Adds support for clause interleaving: + * Methods can now have as many type clauses as they like, this allows to have type bounds depend on terms: `def f(x: Int)[A <: x.type]: A` + * + * @see [[http://dotty.epfl.ch/docs/reference/other-new-features/explicit-nulls.html]] + */ + @compileTimeOnly("`clauseInterleaving` can only be used at compile time in import statements") + object clauseInterleaving + /** Experimental support for pure function type syntax * * @see [[https://dotty.epfl.ch/docs/reference/experimental/purefuns]] diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 63ce926355bb..0937777595ca 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -12,12 +12,17 @@ object MiMaFilters { ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language.3.3"), ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$3$u002E3$"), ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$3$u002E3$minusmigration$"), - ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language#experimental.into"), - ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$into$"), ProblemFilters.exclude[MissingClassProblem]("scala.util.boundary"), ProblemFilters.exclude[MissingClassProblem]("scala.util.boundary$"), ProblemFilters.exclude[MissingClassProblem]("scala.util.boundary$Break"), - ProblemFilters.exclude[MissingClassProblem]("scala.util.boundary$Label") + ProblemFilters.exclude[MissingClassProblem]("scala.util.boundary$Label"), + + // New experimental features in 3.3.X + ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language#experimental.clauseInterleaving"), + ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$clauseInterleaving$"), + ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language#experimental.into"), + ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$into$"), + // end of New experimental features in 3.3.X ) val TastyCore: Seq[ProblemFilter] = Seq( ProblemFilters.exclude[DirectMissingMethodProblem]("dotty.tools.tasty.TastyBuffer.reset"), diff --git a/scaladoc-testcases/src/tests/extensionParams.scala b/scaladoc-testcases/src/tests/extensionParams.scala index 7892676af2c4..9276bf41f067 100644 --- a/scaladoc-testcases/src/tests/extensionParams.scala +++ b/scaladoc-testcases/src/tests/extensionParams.scala @@ -52,3 +52,12 @@ extension [A <: List[Char]](using String)(using Unit)(a: A)(using Int)(using Num extension (using String)(using Unit)(a: Animal)(using Int)(using Number) def f11(b: Any)(c: Any): Any = ??? + +import scala.language.experimental.clauseInterleaving + +extension (using String)(using Unit)(a: Animal)(using Int)(using Number) + def f13(b: Any)[T](c: T): T + = ??? + def f14[D](b: D)[T](c: T): T + = ??? + diff --git a/scaladoc-testcases/src/tests/methodsAndConstructors.scala b/scaladoc-testcases/src/tests/methodsAndConstructors.scala index b8925c593b4c..132d35035b30 100644 --- a/scaladoc-testcases/src/tests/methodsAndConstructors.scala +++ b/scaladoc-testcases/src/tests/methodsAndConstructors.scala @@ -60,3 +60,8 @@ class Methods: def withImplicitParam2(v: String)(implicit ab: Double, a: Int, b: String): String = ??? + import scala.language.experimental.clauseInterleaving + + def clauseInterleaving[T](x: T)[U](y: U)(using (T, U)): (T, U) + = ??? + diff --git a/scaladoc/src/dotty/tools/scaladoc/api.scala b/scaladoc/src/dotty/tools/scaladoc/api.scala index 90a03658c90e..5af55f76a211 100644 --- a/scaladoc/src/dotty/tools/scaladoc/api.scala +++ b/scaladoc/src/dotty/tools/scaladoc/api.scala @@ -44,24 +44,24 @@ enum Modifier(val name: String, val prefix: Boolean): case Transparent extends Modifier("transparent", true) case Infix extends Modifier("infix", true) -case class ExtensionTarget(name: String, typeParams: Seq[TypeParameter], argsLists: Seq[ParametersList], signature: Signature, dri: DRI, position: Long) +case class ExtensionTarget(name: String, typeParams: Seq[TypeParameter], argsLists: Seq[TermParameterList], signature: Signature, dri: DRI, position: Long) case class ImplicitConversion(from: DRI, to: DRI) trait ImplicitConversionProvider { def conversion: Option[ImplicitConversion] } trait Classlike: def typeParams: Seq[TypeParameter] = Seq.empty - def argsLists: Seq[ParametersList] = Seq.empty + def argsLists: Seq[TermParameterList] = Seq.empty enum Kind(val name: String): case RootPackage extends Kind("") case Package extends Kind("package") - case Class(override val typeParams: Seq[TypeParameter], override val argsLists: Seq[ParametersList]) + case Class(override val typeParams: Seq[TypeParameter], override val argsLists: Seq[TermParameterList]) extends Kind("class") with Classlike case Object extends Kind("object") with Classlike - case Trait(override val typeParams: Seq[TypeParameter], override val argsLists: Seq[ParametersList]) + case Trait(override val typeParams: Seq[TypeParameter], override val argsLists: Seq[TermParameterList]) extends Kind("trait") with Classlike - case Enum(override val typeParams: Seq[TypeParameter], override val argsLists: Seq[ParametersList]) extends Kind("enum") with Classlike + case Enum(override val typeParams: Seq[TypeParameter], override val argsLists: Seq[TermParameterList]) extends Kind("enum") with Classlike case EnumCase(kind: Object.type | Kind.Type | Val.type | Class) extends Kind("case") - case Def(typeParams: Seq[TypeParameter], argsLists: Seq[ParametersList]) + case Def(paramLists: Seq[Either[TermParameterList,TypeParameterList]]) extends Kind("def") case Extension(on: ExtensionTarget, m: Kind.Def) extends Kind("def") case Constructor(base: Kind.Def) extends Kind("def") @@ -97,12 +97,12 @@ object Annotation: case class LinkParameter(name: Option[String] = None, dri: DRI, value: String) extends AnnotationParameter case class UnresolvedParameter(name: Option[String] = None, unresolvedText: String) extends AnnotationParameter -case class ParametersList( - parameters: Seq[Parameter], +case class TermParameterList( + parameters: Seq[TermParameter], modifiers: String ) -case class Parameter( +case class TermParameter( annotations: Seq[Annotation], modifiers: String, name: Option[String], @@ -112,6 +112,8 @@ case class Parameter( isGrouped: Boolean = false ) +type TypeParameterList = Seq[TypeParameter] + case class TypeParameter( annotations: Seq[Annotation], variance: "" | "+" | "-", diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala index 5d5f3e9b20d5..f2501378bf72 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/MemberRenderer.scala @@ -409,10 +409,10 @@ class MemberRenderer(signatureRenderer: SignatureRenderer)(using DocContext) ext case (Some(on), members) => val typeSig = SignatureBuilder() .keyword("extension ") - .generics(on.typeParams) + .typeParamList(on.typeParams) .content val argsSig = SignatureBuilder() - .functionParameters(on.argsLists) + .functionTermParameters(on.argsLists) .content val sig = typeSig ++ Signature(Plain(s"(${on.name}: ")) ++ on.signature ++ Signature(Plain(")")) ++ argsSig MGroup(span(cls := "groupHeader")(sig.map(renderElement(_))), members.sortBy(_.name).toSeq, on.name) -> on.position diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala index bae43980a11d..43b4440bce2c 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/Resources.scala @@ -180,10 +180,10 @@ trait Resources(using ctx: DocContext) extends Locations, Writer: case Kind.Extension(on, _) => val typeSig = SignatureBuilder() .keyword("extension ") - .generics(on.typeParams) + .typeParamList(on.typeParams) .content val argsSig = SignatureBuilder() - .functionParameters(on.argsLists) + .functionTermParameters(on.argsLists) .content flattenToText(typeSig ++ argsSig) case _ => "" diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala index 38cc90330265..0c85dff0879f 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala @@ -12,6 +12,9 @@ import NameNormalizer._ import SyntheticsSupport._ import dotty.tools.dotc.core.NameKinds +// Please use this only for things defined in the api.scala file +import dotty.tools.{scaladoc => api} + trait ClassLikeSupport: self: TastyParser => import qctx.reflect._ @@ -45,7 +48,7 @@ trait ClassLikeSupport: .filter(s => s.exists && !s.isHiddenByVisibility) .map( _.tree.asInstanceOf[DefDef]) constr.fold(Nil)( - _.termParamss.map(pList => ParametersList(pList.params.map(p => mkParameter(p, parameterModifier)), paramListModifier(pList.params))) + _.termParamss.map(pList => api.TermParameterList(pList.params.map(p => mkParameter(p, parameterModifier)), paramListModifier(pList.params))) ) if classDef.symbol.flags.is(Flags.Module) then Kind.Object @@ -142,11 +145,12 @@ trait ClassLikeSupport: dd.symbol.extendedSymbol.map { extSym => val memberInfo = unwrapMemberInfo(c, dd.symbol) val typeParams = dd.symbol.extendedTypeParams.map(mkTypeArgument(_, memberInfo.genericTypes)) - val termParams = dd.symbol.extendedTermParamLists.zipWithIndex.flatMap { case (paramList, index) => - memberInfo.paramLists(index) match - case EvidenceOnlyParameterList => Nil - case info: RegularParameterList => - Seq(ParametersList(paramList.params.map(mkParameter(_, memberInfo = info)), paramListModifier(paramList.params))) + val termParams = dd.symbol.extendedTermParamLists.zipWithIndex.flatMap { case (termParamList, index) => + memberInfo.termParamLists(index) match + case MemberInfo.EvidenceOnlyParameterList => None + case MemberInfo.RegularParameterList(info) => + Some(api.TermParameterList(termParamList.params.map(mkParameter(_, memberInfo = info)), paramListModifier(termParamList.params))) + case _ => assert(false, "memberInfo.termParamLists contains a type parameter list !") } val target = ExtensionTarget( extSym.symbol.normalizedName, @@ -335,44 +339,67 @@ trait ClassLikeSupport: def parseMethod( c: ClassDef, methodSymbol: Symbol, - emptyParamsList: Boolean = false, paramPrefix: Symbol => String = _ => "", specificKind: (Kind.Def => Kind) = identity ): Member = val method = methodSymbol.tree.asInstanceOf[DefDef] - val paramLists: List[TermParamClause] = methodSymbol.nonExtensionTermParamLists - val genericTypes: List[TypeDef] = if (methodSymbol.isClassConstructor) Nil else methodSymbol.nonExtensionLeadingTypeParams + val paramLists = methodSymbol.nonExtensionParamLists val memberInfo = unwrapMemberInfo(c, methodSymbol) - val basicKind: Kind.Def = Kind.Def( - genericTypes.map(mkTypeArgument(_, memberInfo.genericTypes, memberInfo.contextBounds)), - paramLists.zipWithIndex.flatMap { (pList, index) => - memberInfo.paramLists(index) match - case EvidenceOnlyParameterList => Nil - case info: RegularParameterList => - Seq(ParametersList(pList.params.map( + val unshuffledMemberInfoParamLists = + if methodSymbol.isExtensionMethod && methodSymbol.isRightAssoc then + // Taken from RefinedPrinter.scala + // If you change the names of the clauses below, also change them in right-associative-extension-methods.md + val (leftTyParams, rest1) = memberInfo.paramLists.span(_.isType) + val (leadingUsing, rest2) = rest1.span(_.isUsing) + val (rightTyParams, rest3) = rest2.span(_.isType) + val (rightParam, rest4) = rest3.splitAt(1) + val (leftParam, rest5) = rest4.splitAt(1) + val (trailingUsing, rest6) = rest5.span(_.isUsing) + if leftParam.nonEmpty then + // leftTyParams ::: leadingUsing ::: leftParam ::: trailingUsing ::: rightTyParams ::: rightParam ::: rest6 + // because of takeRight after, this is equivalent to the following: + rightTyParams ::: rightParam ::: rest6 + else + memberInfo.paramLists // it wasn't a binary operator, after all. + else + memberInfo.paramLists + + val croppedUnshuffledMemberInfoParamLists = unshuffledMemberInfoParamLists.takeRight(paramLists.length) + + val basicDefKind: Kind.Def = Kind.Def( + paramLists.zip(croppedUnshuffledMemberInfoParamLists).flatMap{ + case (_: TermParamClause, MemberInfo.EvidenceOnlyParameterList) => Nil + case (pList: TermParamClause, MemberInfo.RegularParameterList(info)) => + Some(Left(api.TermParameterList(pList.params.map( mkParameter(_, paramPrefix, memberInfo = info)), paramListModifier(pList.params) - )) + ))) + case (TypeParamClause(genericTypeList), MemberInfo.TypeParameterList(memInfoTypes)) => + Some(Right(genericTypeList.map(mkTypeArgument(_, memInfoTypes, memberInfo.contextBounds)))) + case (_,_) => + assert(false, s"croppedUnshuffledMemberInfoParamLists and SymOps.nonExtensionParamLists disagree on whether this clause is a type or term one") } ) val methodKind = - if methodSymbol.isClassConstructor then Kind.Constructor(basicKind) - else if methodSymbol.flags.is(Flags.Implicit) then extractImplicitConversion(method.returnTpt.tpe) match - case Some(conversion) if paramLists.size == 0 || (paramLists.size == 1 && paramLists.head.params.size == 0) => - Kind.Implicit(basicKind, Some(conversion)) - case None if paramLists.size == 1 && paramLists(0).params.size == 1 => - Kind.Implicit(basicKind, Some( - ImplicitConversion( - paramLists(0).params(0).tpt.tpe.typeSymbol.dri, - method.returnTpt.tpe.typeSymbol.dri - ) - )) - case _ => - Kind.Implicit(basicKind, None) - else if methodSymbol.flags.is(Flags.Given) then Kind.Given(basicKind, Some(method.returnTpt.tpe.asSignature), extractImplicitConversion(method.returnTpt.tpe)) - else specificKind(basicKind) + if methodSymbol.isClassConstructor then Kind.Constructor(basicDefKind) + else if methodSymbol.flags.is(Flags.Implicit) then + val termParamLists: List[TermParamClause] = methodSymbol.nonExtensionTermParamLists + extractImplicitConversion(method.returnTpt.tpe) match + case Some(conversion) if termParamLists.size == 0 || (termParamLists.size == 1 && termParamLists.head.params.size == 0) => + Kind.Implicit(basicDefKind, Some(conversion)) + case None if termParamLists.size == 1 && termParamLists(0).params.size == 1 => + Kind.Implicit(basicDefKind, Some( + ImplicitConversion( + termParamLists(0).params(0).tpt.tpe.typeSymbol.dri, + method.returnTpt.tpe.typeSymbol.dri + ) + )) + case _ => + Kind.Implicit(basicDefKind, None) + else if methodSymbol.flags.is(Flags.Given) then Kind.Given(basicDefKind, Some(method.returnTpt.tpe.asSignature), extractImplicitConversion(method.returnTpt.tpe)) + else specificKind(basicDefKind) val origin = if !methodSymbol.isOverridden then Origin.RegularlyDefined else val overriddenSyms = methodSymbol.allOverriddenSymbols.map(_.owner) @@ -404,7 +431,7 @@ trait ClassLikeSupport: val inlinePrefix = if argument.symbol.flags.is(Flags.Inline) then "inline " else "" val nameIfNotSynthetic = Option.when(!argument.symbol.flags.is(Flags.Synthetic))(argument.symbol.normalizedName) val name = argument.symbol.normalizedName - Parameter( + api.TermParameter( argument.symbol.getAnnotations(), inlinePrefix + prefix(argument.symbol), nameIfNotSynthetic, @@ -514,16 +541,26 @@ trait ClassLikeSupport: experimental = experimental ) - object EvidenceOnlyParameterList - type RegularParameterList = Map[String, TypeRepr] - type ParameterList = RegularParameterList | EvidenceOnlyParameterList.type case class MemberInfo( - genericTypes: Map[String, TypeBounds], - paramLists: List[ParameterList], + paramLists: List[MemberInfo.ParameterList], res: TypeRepr, contextBounds: Map[String, DSignature] = Map.empty, - ) + ){ + val genericTypes: Map[String, TypeBounds] = paramLists.collect{ case MemberInfo.TypeParameterList(types) => types }.headOption.getOrElse(Map()) + + val termParamLists: List[MemberInfo.ParameterList] = paramLists.filter(_.isTerm) + } + + object MemberInfo: + enum ParameterList(val isTerm: Boolean, val isUsing: Boolean): + inline def isType = !isTerm + case EvidenceOnlyParameterList extends ParameterList(isTerm = true, isUsing = false) + case RegularParameterList(m: Map[String, TypeRepr])(isUsing: Boolean) extends ParameterList(isTerm = true, isUsing) + case TypeParameterList(m: Map[String, TypeBounds]) extends ParameterList(isTerm = false, isUsing = false) + + export ParameterList.{RegularParameterList, EvidenceOnlyParameterList, TypeParameterList} + def unwrapMemberInfo(c: ClassDef, symbol: Symbol): MemberInfo = @@ -541,10 +578,12 @@ trait ClassLikeSupport: symbol.paramSymss.flatten.find(_.name == name).exists(_.flags.is(Flags.Implicit)) def handlePolyType(memberInfo: MemberInfo, polyType: PolyType): MemberInfo = - MemberInfo(polyType.paramNames.zip(polyType.paramBounds).toMap, memberInfo.paramLists, polyType.resType) + val typeParamList = MemberInfo.TypeParameterList(polyType.paramNames.zip(polyType.paramBounds).toMap) + MemberInfo(memberInfo.paramLists :+ typeParamList, polyType.resType) def handleMethodType(memberInfo: MemberInfo, methodType: MethodType): MemberInfo = val rawParams = methodType.paramNames.zip(methodType.paramTypes).toMap + val isUsing = methodType.isImplicit val (evidences, notEvidences) = rawParams.partition(e => isSyntheticEvidence(e._1)) def findParamRefs(t: TypeRepr): Seq[ParamRef] = t match @@ -573,14 +612,15 @@ trait ClassLikeSupport: val newParams = notEvidences ++ paramsThatLookLikeContextBounds - val newLists: List[ParameterList] = if newParams.isEmpty && contextBounds.nonEmpty - then memberInfo.paramLists ++ Seq(EvidenceOnlyParameterList) - else memberInfo.paramLists ++ Seq(newParams) + val termParamList = if newParams.isEmpty && contextBounds.nonEmpty + then MemberInfo.EvidenceOnlyParameterList + else MemberInfo.RegularParameterList(newParams)(isUsing) + - MemberInfo(memberInfo.genericTypes, newLists , methodType.resType, contextBounds.toMap) + MemberInfo(memberInfo.paramLists :+ termParamList, methodType.resType, contextBounds.toMap) def handleByNameType(memberInfo: MemberInfo, byNameType: ByNameType): MemberInfo = - MemberInfo(memberInfo.genericTypes, memberInfo.paramLists, byNameType.underlying) + MemberInfo(memberInfo.paramLists, byNameType.underlying) def recursivelyCalculateMemberInfo(memberInfo: MemberInfo): MemberInfo = memberInfo.res match case p: PolyType => recursivelyCalculateMemberInfo(handlePolyType(memberInfo, p)) @@ -588,7 +628,7 @@ trait ClassLikeSupport: case b: ByNameType => handleByNameType(memberInfo, b) case _ => memberInfo - recursivelyCalculateMemberInfo(MemberInfo(Map.empty, List.empty, baseTypeRepr)) + recursivelyCalculateMemberInfo(MemberInfo(List.empty, baseTypeRepr)) private def paramListModifier(parameters: Seq[ValDef]): String = if parameters.size > 0 then diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala index b4a1fc197d9a..584ef1f54267 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala @@ -143,7 +143,9 @@ object SymOps: import reflect._ sym.flags.is(Flags.Artifact) - def isLeftAssoc: Boolean = !sym.name.endsWith(":") + def isRightAssoc: Boolean = sym.name.endsWith(":") + + def isLeftAssoc: Boolean = !sym.isRightAssoc def extendedSymbol: Option[reflect.ValDef] = import reflect.* @@ -172,41 +174,36 @@ object SymOps: ) case _ => Nil -> Nil + def extendedParamLists: List[reflect.ParamClause] = sym.splitExtensionParamList._1 + + def extendedTypeParamLists: List[reflect.TypeParamClause] = + sym.extendedParamLists.collect { + case typeClause: reflect.TypeParamClause => typeClause + } + def extendedTypeParams: List[reflect.TypeDef] = - import reflect.* - sym.tree match - case tree: DefDef => - tree.leadingTypeParams - case _ => Nil + sym.extendedTypeParamLists.headOption.map(_.params).getOrElse(List()) def extendedTermParamLists: List[reflect.TermParamClause] = - import reflect.* - sym.splitExtensionParamList._1.collect { - case tpc: TermParamClause => tpc + sym.extendedParamLists.collect { + case tpc: reflect.TermParamClause => tpc } - def nonExtensionTermParamLists: List[reflect.TermParamClause] = - import reflect.* - if sym.nonExtensionLeadingTypeParams.nonEmpty then - sym.nonExtensionParamLists.dropWhile { - case _: TypeParamClause => false - case _ => true - }.drop(1).collect { - case tpc: TermParamClause => tpc - } - else - sym.nonExtensionParamLists.collect { - case tpc: TermParamClause => tpc - } - def nonExtensionParamLists: List[reflect.ParamClause] = sym.splitExtensionParamList._2 + def nonExtensionTermParamLists: List[reflect.TermParamClause] = + sym.nonExtensionParamLists.collect { + case tpc: reflect.TermParamClause => tpc + } + + def nonExtensionTypeParamLists: List[reflect.TypeParamClause] = + sym.nonExtensionParamLists.collect { + case typeClause: reflect.TypeParamClause => typeClause + } + def nonExtensionLeadingTypeParams: List[reflect.TypeDef] = - import reflect.* - sym.nonExtensionParamLists.collectFirst { - case TypeParamClause(params) => params - }.toList.flatten + sym.nonExtensionTypeParamLists.headOption.map(_.params).getOrElse(List()) end extension diff --git a/scaladoc/src/dotty/tools/scaladoc/transformers/ImplicitMembersExtensionTransformer.scala b/scaladoc/src/dotty/tools/scaladoc/transformers/ImplicitMembersExtensionTransformer.scala index 44eba3a39807..8ed7436bb11d 100644 --- a/scaladoc/src/dotty/tools/scaladoc/transformers/ImplicitMembersExtensionTransformer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/transformers/ImplicitMembersExtensionTransformer.scala @@ -29,7 +29,7 @@ class ImplicitMembersExtensionTransformer(using DocContext) extends(Module => Mo case m @ Member(_, _, _, Kind.Extension(ExtensionTarget(_, _, _, _, MyDri, _), _), Origin.RegularlyDefined) => val kind = m.kind match case Kind.Extension(_, d) => d - case _ => Kind.Def(Nil, Nil) + case _ => Kind.Def(Nil) Seq(m.withOrigin(Origin.ExtensionFrom(source.name, source.dri)).withKind(kind)) case m @ Member(_, _, _, conversionProvider: ImplicitConversionProvider, Origin.RegularlyDefined) => diff --git a/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureProvider.scala b/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureProvider.scala index 88561282afb0..fd8dfc4f5b6c 100644 --- a/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureProvider.scala +++ b/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureProvider.scala @@ -58,8 +58,8 @@ class ScalaSignatureProvider: builder.kind(showKind), builder.name(member.name, member.dri), builder - .generics(kind.typeParams) - .functionParameters(kind.argsLists) + .typeParamList(kind.typeParams) + .functionTermParameters(kind.argsLists) .parentsSignature(member) ) @@ -106,8 +106,7 @@ class ScalaSignatureProvider: builder.kind(showKind), builder.name(method.name, method.dri), builder - .generics(kind.typeParams) - .functionParameters(kind.argsLists) + .functionParameters(kind.paramLists) .pipe { builder => instance.fold(builder)(i => builder.plain(": ").signature(i)) } @@ -151,7 +150,7 @@ class ScalaSignatureProvider: builder.modifiersAndVisibility(typeDef), builder.kind(tpe), builder.name(typeDef.name, typeDef.dri), - builder.generics(tpe.typeParams).pipe { bdr => + builder.typeParamList(tpe.typeParams).pipe { bdr => if (!tpe.opaque) { (if tpe.concreate then bdr.plain(" = ") else bdr) .signature(typeDef.signature) diff --git a/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureUtils.scala b/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureUtils.scala index acbfe87b5d25..d28dd6ca18fe 100644 --- a/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureUtils.scala +++ b/scaladoc/src/dotty/tools/scaladoc/translators/ScalaSignatureUtils.scala @@ -26,7 +26,7 @@ case class SignatureBuilder(content: Signature = Nil) extends ScalaSignatureUtil def annotationsBlock(d: Member): SignatureBuilder = d.annotations.foldLeft(this){ (bdr, annotation) => bdr.buildAnnotation(annotation)} - def annotationsInline(d: Parameter): SignatureBuilder = + def annotationsInline(d: TermParameter): SignatureBuilder = d.annotations.foldLeft(this){ (bdr, annotation) => bdr.buildAnnotation(annotation) } def annotationsInline(t: TypeParameter): SignatureBuilder = @@ -74,21 +74,27 @@ case class SignatureBuilder(content: Signature = Nil) extends ScalaSignatureUtil def kind(k: Kind) = keyword(k.name + " ") - def generics(on: Seq[TypeParameter]) = list(on.toList, List(Plain("[")), List(Plain("]"))){ (bdr, e) => + + def functionParameters(paramss: Seq[ Either[TermParameterList,TypeParameterList] ]) = + this.list(paramss, separator = List(Plain(""))) { + case (bld, Left(params: TermParameterList)) => bld.termParamList(params) + case (bld, Right(params: TypeParameterList)) => bld.typeParamList(params) + } + + def termParamList(params: TermParameterList) = + this.list(params.parameters, prefix = List(Plain("("), Keyword(params.modifiers)), suffix = List(Plain(")")), forcePrefixAndSuffix = true) { (bld, p) => + val annotationsAndModifiers = bld.annotationsInline(p) + .keyword(p.modifiers) + val name = p.name.fold(annotationsAndModifiers)(annotationsAndModifiers.name(_, p.dri).plain(": ")) + name.signature(p.signature) + } + + def typeParamList(on: TypeParameterList) = list(on.toList, List(Plain("[")), List(Plain("]"))){ (bdr, e) => bdr.annotationsInline(e).keyword(e.variance).tpe(e.name, Some(e.dri)).signature(e.signature) } - def functionParameters(params: Seq[ParametersList]) = - if params.isEmpty then this.plain("") - else if params.size == 1 && params(0).parameters == Nil then this.plain("()") - else this.list(params, separator = List(Plain(""))) { (bld, pList) => - bld.list(pList.parameters, prefix = List(Plain("("), Keyword(pList.modifiers)), suffix = List(Plain(")")), forcePrefixAndSuffix = true) { (bld, p) => - val annotationsAndModifiers = bld.annotationsInline(p) - .keyword(p.modifiers) - val name = p.name.fold(annotationsAndModifiers)(annotationsAndModifiers.name(_, p.dri).plain(": ")) - name.signature(p.signature) - } - } + def functionTermParameters(paramss: Seq[TermParameterList]) = + this.list(paramss, separator = List(Plain(""))) { (bld, pList) => bld.termParamList(pList) } trait ScalaSignatureUtils: extension (tokens: Seq[String]) def toSignatureString(): String = diff --git a/tests/neg/extension-methods.scala b/tests/neg/extension-methods.scala index e075105762f9..a11b2cca5add 100644 --- a/tests/neg/extension-methods.scala +++ b/tests/neg/extension-methods.scala @@ -15,4 +15,4 @@ object Test { def f2[T]: T = ??? // error: T is already defined as type T def f3(xs: List[T]) = ??? // error: xs is already defined as value xs } -} \ No newline at end of file +} diff --git a/tests/neg/interleaving-ab.scala b/tests/neg/interleaving-ab.scala new file mode 100644 index 000000000000..e446626a2982 --- /dev/null +++ b/tests/neg/interleaving-ab.scala @@ -0,0 +1,11 @@ +import scala.language.experimental.clauseInterleaving + +object Ab: + given String = "" + given Double = 0 + + def illegal[A][B](x: A)(using B): B = summon[B] // error: Type parameter lists must be separated by a term or using parameter list + + def ab[A](x: A)[B](using B): B = summon[B] + def test = + ab[Int](0: Int) // error diff --git a/tests/neg/interleaving-params.scala b/tests/neg/interleaving-params.scala new file mode 100644 index 000000000000..dc6762cf0214 --- /dev/null +++ b/tests/neg/interleaving-params.scala @@ -0,0 +1,9 @@ +import scala.language.experimental.clauseInterleaving + +class Params{ + def bar[T](x: T)[T]: String = ??? // error + def zoo(x: Int)[T, U](x: U): T = ??? // error + def bbb[T <: U](x: U)[U]: U = ??? // error // error + def f0[T](implicit x: T)[U](y: U) = (x,y) // error + def f1[T](implicit x: T)[U] = (x,y) // error +} diff --git a/tests/neg/interleaving-signatureCollision.scala b/tests/neg/interleaving-signatureCollision.scala new file mode 100644 index 000000000000..a6a729ed3b62 --- /dev/null +++ b/tests/neg/interleaving-signatureCollision.scala @@ -0,0 +1,5 @@ +import scala.language.experimental.clauseInterleaving + +object signatureCollision: + def f[T](x: T)[U](y: U) = (x,y) + def f[T](x: T, y: T) = (x,y) // error diff --git a/tests/neg/interleaving-typeApply.check b/tests/neg/interleaving-typeApply.check new file mode 100644 index 000000000000..a50c1455bfbb --- /dev/null +++ b/tests/neg/interleaving-typeApply.check @@ -0,0 +1,30 @@ +-- [E057] Type Mismatch Error: tests/neg/interleaving-typeApply.scala:10:11 -------------------------------------------- +10 | f3[String]() // error + | ^ + | Type argument String does not conform to upper bound Int + | + | longer explanation available when compiling with `-explain` +-- [E057] Type Mismatch Error: tests/neg/interleaving-typeApply.scala:11:16 -------------------------------------------- +11 | f5[Int][Unit] // error + | ^ + | Type argument Unit does not conform to upper bound String + | + | longer explanation available when compiling with `-explain` +-- [E057] Type Mismatch Error: tests/neg/interleaving-typeApply.scala:12:19 -------------------------------------------- +12 | f5[String][Unit] // error // error + | ^ + | Type argument Unit does not conform to upper bound String + | + | longer explanation available when compiling with `-explain` +-- [E057] Type Mismatch Error: tests/neg/interleaving-typeApply.scala:12:11 -------------------------------------------- +12 | f5[String][Unit] // error // error + | ^ + | Type argument String does not conform to upper bound Int + | + | longer explanation available when compiling with `-explain` +-- [E057] Type Mismatch Error: tests/neg/interleaving-typeApply.scala:13:11 -------------------------------------------- +13 | f7[String]()[Unit] // error + | ^ + | Type argument String does not conform to upper bound Int + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/interleaving-typeApply.scala b/tests/neg/interleaving-typeApply.scala new file mode 100644 index 000000000000..ad21fe2f0329 --- /dev/null +++ b/tests/neg/interleaving-typeApply.scala @@ -0,0 +1,14 @@ +import scala.language.experimental.clauseInterleaving + +object typeApply: + + def f3[T <: Int](using DummyImplicit)[U <: String](): T => T = ??? + def f5[T <: Int](using DummyImplicit)[U <: String]: [X <: Unit] => X => X = ??? + def f7[T <: Int](using DummyImplicit)[U <: String]()[X <: Unit]: X => X = ??? + + @main def test = { + f3[String]() // error + f5[Int][Unit] // error + f5[String][Unit] // error // error + f7[String]()[Unit] // error + } diff --git a/tests/neg/interleaving-unmatched.scala b/tests/neg/interleaving-unmatched.scala new file mode 100644 index 000000000000..2ce3074d07fa --- /dev/null +++ b/tests/neg/interleaving-unmatched.scala @@ -0,0 +1,5 @@ +import scala.language.experimental.clauseInterleaving + +object unmatched: + def f1[T (x: T)] = ??? // error + def f2(x: Any[)T] = ??? // error // error diff --git a/tests/neg/namedTypeParams.check b/tests/neg/namedTypeParams.check new file mode 100644 index 000000000000..3f6f9f7913e8 --- /dev/null +++ b/tests/neg/namedTypeParams.check @@ -0,0 +1,102 @@ +-- [E040] Syntax Error: tests/neg/namedTypeParams.scala:2:8 ------------------------------------------------------------ +2 |class D[type T] // error: identifier expected, but `type` found + | ^^^^ + | an identifier expected, but 'type' found + | + | longer explanation available when compiling with `-explain` +-- [E040] Syntax Error: tests/neg/namedTypeParams.scala:11:13 ---------------------------------------------------------- +11 | val x: C[T = Int] = // error: ']' expected, but `=` found // error + | ^ + | ']' expected, but '=' found +-- [E040] Syntax Error: tests/neg/namedTypeParams.scala:12:12 ---------------------------------------------------------- +12 | new C[T = Int] // error: ']' expected, but `=` found // error + | ^ + | ']' expected, but '=' found +-- [E040] Syntax Error: tests/neg/namedTypeParams.scala:14:22 ---------------------------------------------------------- +14 | class E extends C[T = Int] // error: ']' expected, but `=` found // error + | ^ + | ']' expected, but '=' found +-- [E040] Syntax Error: tests/neg/namedTypeParams.scala:15:22 ---------------------------------------------------------- +15 | class F extends C[T = Int]() // error: ']' expected, but `=` found // error + | ^ + | ']' expected, but '=' found +-- [E040] Syntax Error: tests/neg/namedTypeParams.scala:19:19 ---------------------------------------------------------- +19 | f[X = Int, String](1, "") // error // error + | ^ + | '=' expected, but ']' found +-- Error: tests/neg/namedTypeParams.scala:6:8 -------------------------------------------------------------------------- +6 | f[X = Int, Y = Int](1, 2) // error: experimental // error: experimental + | ^^^ + | Named type arguments are experimental, + | they must be enabled with a `experimental.namedTypeArguments` language import or setting +-- Error: tests/neg/namedTypeParams.scala:6:17 ------------------------------------------------------------------------- +6 | f[X = Int, Y = Int](1, 2) // error: experimental // error: experimental + | ^^^ + | Named type arguments are experimental, + | they must be enabled with a `experimental.namedTypeArguments` language import or setting +-- [E006] Not Found Error: tests/neg/namedTypeParams.scala:11:11 ------------------------------------------------------- +11 | val x: C[T = Int] = // error: ']' expected, but `=` found // error + | ^ + | Not found: type T + | + | longer explanation available when compiling with `-explain` +-- [E006] Not Found Error: tests/neg/namedTypeParams.scala:12:10 ------------------------------------------------------- +12 | new C[T = Int] // error: ']' expected, but `=` found // error + | ^ + | Not found: type T + | + | longer explanation available when compiling with `-explain` +-- [E006] Not Found Error: tests/neg/namedTypeParams.scala:14:20 ------------------------------------------------------- +14 | class E extends C[T = Int] // error: ']' expected, but `=` found // error + | ^ + | Not found: type T + | + | longer explanation available when compiling with `-explain` +-- [E006] Not Found Error: tests/neg/namedTypeParams.scala:15:20 ------------------------------------------------------- +15 | class F extends C[T = Int]() // error: ']' expected, but `=` found // error + | ^ + | Not found: type T + | + | longer explanation available when compiling with `-explain` +-- [E102] Syntax Error: tests/neg/namedTypeParams.scala:19:18 ---------------------------------------------------------- +19 | f[X = Int, String](1, "") // error // error + | ^ + | Type parameter String is undefined. Expected one of X, Y. +-- Error: tests/neg/namedTypeParams.scala:20:12 ------------------------------------------------------------------------ +20 | f[X = Int][X = Int][Y = String](1, "") // error: illegal repeated type application + | ^^^^^^^^^^^^^^^^^^^ + | illegal repeated type application + | You might have meant something like: + | Test.f[X = Int, X = Int] +-- Error: tests/neg/namedTypeParams.scala:22:12 ------------------------------------------------------------------------ +22 | f[X = Int][Y = String](1, "") // error: illegal repeated type application + | ^^^^^^^^^^^^^^^^^^^^^^ + | illegal repeated type application + | You might have meant something like: + | Test.f[X = Int, Y = String] +-- Error: tests/neg/namedTypeParams.scala:23:12 ------------------------------------------------------------------------ +23 | f[X = Int][String](1, "") // error: illegal repeated type application + | ^^^^^^^^^^^^^^^^^^ + | illegal repeated type application + | You might have meant something like: + | Test.f[X = Int, String] +-- Error: tests/neg/namedTypeParams.scala:25:15 ------------------------------------------------------------------------ +25 | f[Y = String][X = Int](1, "") // error: illegal repeated type application + | ^^^^^^^^^^^^^^^^^^^^^^ + | illegal repeated type application + | You might have meant something like: + | Test.f[Y = String, X = Int] +-- Error: tests/neg/namedTypeParams.scala:26:15 ------------------------------------------------------------------------ +26 | f[Y = String][Int](1, "") // error: illegal repeated type application + | ^^^^^^^^^^^^^^^^^^ + | illegal repeated type application + | You might have meant something like: + | Test.f[Y = String, Int] +-- [E102] Syntax Error: tests/neg/namedTypeParams.scala:33:9 ----------------------------------------------------------- +33 | f2[Y = String][X = Int](1, "") // error: Y is undefined + | ^^^^^^ + | Type parameter Y is undefined. Expected one of X. +-- [E102] Syntax Error: tests/neg/namedTypeParams.scala:34:9 ----------------------------------------------------------- +34 | f2[Y = String](1, "") // error: Y is undefined + | ^^^^^^ + | Type parameter Y is undefined. Expected one of X. diff --git a/tests/neg/namedTypeParams.scala b/tests/neg/namedTypeParams.scala index 8ed7c92241ea..53ef14188e12 100644 --- a/tests/neg/namedTypeParams.scala +++ b/tests/neg/namedTypeParams.scala @@ -5,7 +5,7 @@ object Test0: def f[X, Y](x: X, y: Y): Int = ??? f[X = Int, Y = Int](1, 2) // error: experimental // error: experimental -object Test { +object Test: import language.experimental.namedTypeArguments val x: C[T = Int] = // error: ']' expected, but `=` found // error @@ -24,4 +24,11 @@ object Test { f[Y = String][X = Int](1, "") // error: illegal repeated type application f[Y = String][Int](1, "") // error: illegal repeated type application -} + +object TestInterleaving: + import language.experimental.namedTypeArguments + import language.experimental.clauseInterleaving + def f2[X](using DummyImplicit)[Y](x: X, y: Y): Int = ??? + + f2[Y = String][X = Int](1, "") // error: Y is undefined + f2[Y = String](1, "") // error: Y is undefined diff --git a/tests/neg/overrides.scala b/tests/neg/overrides.scala index 48f3260721e9..c8fc8de97f7c 100644 --- a/tests/neg/overrides.scala +++ b/tests/neg/overrides.scala @@ -42,6 +42,9 @@ class A[T] { def next: T = ??? + import scala.language.experimental.clauseInterleaving + + def b[U <: T](x: Int)[V >: T](y: String) = false } class B extends A[Int] { @@ -52,6 +55,20 @@ class B extends A[Int] { override def next(): Int = ??? // error: incompatible type + import scala.language.experimental.clauseInterleaving + + override def b[T <: Int](x: Int)(y: String) = true // error +} + +class C extends A[String] { + + override def f(x: String) = x // error + + override def next: Int = ??? // error: incompatible type + + import scala.language.experimental.clauseInterleaving + + override def b[T <: String](x: Int)[U >: Int](y: String) = true // error: incompatible type } class X { @@ -103,4 +120,3 @@ class C extends A { override def m: Int = 42 // error: has incompatible type } } - diff --git a/tests/pos/interleaving-ba.scala b/tests/pos/interleaving-ba.scala new file mode 100644 index 000000000000..69fe2d9537a0 --- /dev/null +++ b/tests/pos/interleaving-ba.scala @@ -0,0 +1,11 @@ +import scala.language.experimental.clauseInterleaving + +object BA { + given String = "" + given Double = 0 + + def ba[A](x: A)[B](using B): B = summon[B] + + def test = ba(0)[String] + +} diff --git a/tests/pos/interleaving-chainedParams.scala b/tests/pos/interleaving-chainedParams.scala new file mode 100644 index 000000000000..e502888d97c8 --- /dev/null +++ b/tests/pos/interleaving-chainedParams.scala @@ -0,0 +1,20 @@ +import scala.language.experimental.clauseInterleaving + +object chainedParams{ + + trait Chain{ + type Tail <: Chain + } + + def f[C1 <: Chain](c1: C1)[C2 <: c1.Tail](c2: C2)[C3 <: c2.Tail](c3: C3): c3.Tail = ??? + + val self = new Chain{ type Tail = this.type } + val res: self.type = f(self)(self)(self) + + type C <: Chain + + val c3 = new Chain{ type Tail = C } + val c2 = new Chain{ type Tail = c3.type } + val c1 = new Chain{ type Tail = c2.type } + val u: C = f(c1)(c2)(c3) +} diff --git a/tests/pos/interleaving-classless.scala b/tests/pos/interleaving-classless.scala new file mode 100644 index 000000000000..5aec92db3409 --- /dev/null +++ b/tests/pos/interleaving-classless.scala @@ -0,0 +1,6 @@ +import scala.language.experimental.clauseInterleaving + +def f1[T]()[U](x: T, y: U): (T, U) = (x, y) +def f2[T](x: T)[U](y: U): (T, U) = (x, y) +def f3[T, U](using DummyImplicit)[V](x: T): U = ??? +def f4[T](x: T)[U <: x.type](y: U): (T, U) = (x, y) diff --git a/tests/pos/interleaving-functor.scala b/tests/pos/interleaving-functor.scala new file mode 100644 index 000000000000..35bed59f77f0 --- /dev/null +++ b/tests/pos/interleaving-functor.scala @@ -0,0 +1,19 @@ +import scala.language.experimental.clauseInterleaving + +object functorInterleaving: + //taken from https://dotty.epfl.ch/docs/reference/contextual/type-classes.html + //at version 3.1.1-RC1-bin-20210930-01f040b-NIGHTLY + //modified to have type interleaving + trait Functor[F[_]]: + def map[A](x: F[A])[B](f: A => B): F[B] + + + given Functor[List] with + def map[A](x: List[A])[B](f: A => B): List[B] = + x.map(f) + + def assertTransformation[F[_]: Functor, A](original: F[A])[B](expected: F[B])(mapping: A => B): Unit = + assert(expected == summon[Functor[F]].map(original)(mapping)) + + @main def testInterweaving = + assertTransformation(List("a", "b"))(List("a1", "b1")){elt => s"${elt}1"} diff --git a/tests/pos/interleaving-newline.scala b/tests/pos/interleaving-newline.scala new file mode 100644 index 000000000000..de8fb98a2f81 --- /dev/null +++ b/tests/pos/interleaving-newline.scala @@ -0,0 +1,11 @@ +import scala.language.experimental.clauseInterleaving + +object newline { + def multipleLines + [T] + (x: T) + [U] + (using (T,U)) + (y: U) + = ??? +} diff --git a/tests/pos/interleaving-overload.scala b/tests/pos/interleaving-overload.scala new file mode 100644 index 000000000000..1902551f9036 --- /dev/null +++ b/tests/pos/interleaving-overload.scala @@ -0,0 +1,20 @@ +import scala.language.experimental.clauseInterleaving + +class A{ + + def f1[T](x: Any)[U] = ??? + def f1[T](x: Int)[U] = ??? + + f1(1) + f1("hello") + + case class B[U](x: Int) + def b[U](x: Int) = B[U](x) + + def f2[T]: [U] => Int => B[U] = [U] => (x: Int) => b[U](x) + + f2(1) + f2[Any](1) + f2[Any][Any](1) + +} diff --git a/tests/pos/interleaving-params.scala b/tests/pos/interleaving-params.scala new file mode 100644 index 000000000000..36963ff2e123 --- /dev/null +++ b/tests/pos/interleaving-params.scala @@ -0,0 +1,8 @@ +import scala.language.experimental.clauseInterleaving + +class Params{ + type U + def foo[T](x: T)[U >: x.type <: T](using U)[L <: List[U]](l: L): L = ??? + def aaa(x: U): U = ??? + def bbb[T <: U](x: U)[U]: U = ??? +} diff --git a/tests/pos/interleaving-signatureCollision.scala b/tests/pos/interleaving-signatureCollision.scala new file mode 100644 index 000000000000..77190284ae6d --- /dev/null +++ b/tests/pos/interleaving-signatureCollision.scala @@ -0,0 +1,6 @@ +import scala.language.experimental.clauseInterleaving +import scala.annotation.targetName + +object signatureCollision: + def f[T](x: T)[U](y: U) = (x,y) + @targetName("g") def f[T](x: T, y: T) = (x,y) diff --git a/tests/pos/interleaving-typeApply.scala b/tests/pos/interleaving-typeApply.scala new file mode 100644 index 000000000000..3c669cc76bfc --- /dev/null +++ b/tests/pos/interleaving-typeApply.scala @@ -0,0 +1,25 @@ +import scala.language.experimental.clauseInterleaving + +object typeApply: + + def f0[T]: [U] => T => T = ??? + def f1[T](using DummyImplicit)[U]: T => T = ??? + def f2[T](using DummyImplicit)[U](): T => T = ??? + def f3[T <: Int](using DummyImplicit)[U <: String](): T => T = ??? + def f4[T <: Int](using DummyImplicit)[U <: String]: T => T = ??? + def f5[T <: Int](using DummyImplicit)[U <: String]: [X <: Unit] => X => X = ??? + def f6[T <: Int](using DummyImplicit)[U <: String](): [X <: Unit] => X => X = ??? + def f7[T <: Int](using DummyImplicit)[U <: String]()[X <: Unit]: X => X = ??? + + @main def test = { + import scala.language.experimental.namedTypeArguments + f0[Int][String] + f1[Int][String] + f2[Int][String]() + f3[Int][String]() + f4[Int][String] + f5[Int][String] + f5[Int][String][Unit] + f6[Int]()[Unit] + f7[Int]()[Unit] + } diff --git a/tests/pos/namedTypeParams.scala b/tests/pos/namedTypeParams.scala index a8c38972838c..388bcfa98bef 100644 --- a/tests/pos/namedTypeParams.scala +++ b/tests/pos/namedTypeParams.scala @@ -1,4 +1,5 @@ import language.experimental.namedTypeArguments + object Test { def f[X, Y](x: X, y: Y): Int = ??? @@ -7,6 +8,14 @@ object Test { f[X = Int, Y = String](1, "") f[X = Int](1, "") f[Y = String](1, "") +} + +object TestInterleaving{ + import language.experimental.clauseInterleaving + def f2[X](using DummyImplicit)[Y](x: X, y: Y): Int = ??? + + f2[X = Int][Y = String](1, "") + f2[X = Int](1, "") } diff --git a/tests/pos/overrides.scala b/tests/pos/overrides.scala index 97402f773082..146dc06c76a9 100644 --- a/tests/pos/overrides.scala +++ b/tests/pos/overrides.scala @@ -1,13 +1,21 @@ class A[T] { def f(x: T)(y: T = x) = y + + import scala.language.experimental.clauseInterleaving -} + def b[U <: T](x: Int)[V >: T](y: String) = false +} class B extends A[Int] { override def f(x: Int)(y: Int) = y f(2)() + + import scala.language.experimental.clauseInterleaving + + override def b[T <: Int](x: Int)[U >: Int](y: String) = true + } diff --git a/tests/run/interleaving.scala b/tests/run/interleaving.scala new file mode 100644 index 000000000000..557741032e8a --- /dev/null +++ b/tests/run/interleaving.scala @@ -0,0 +1,102 @@ +object Test extends App { + import scala.language.experimental.clauseInterleaving + trait Key { type Value } + trait DB { + def getOrElse(k: Key)[V >: k.Value](default: V): V // dependent type parameter + } + + val key1 = new Key{ type Value = Some[Int] } + val key2 = new Key{ type Value = Some[Int] } + val key3 = new Key{ type Value = Some[String] } + + val db1: DB = new DB{ + def getOrElse(k: Key)[V >: k.Value](default: V): V = if k == key1 then Some(4).asInstanceOf[k.Value] else default + } + + // Interleaved method with dependent type bound + val default1: None.type = None + assert(db1.getOrElse(key1)[Option[Int]](default1) == Some(4)) + assert(db1.getOrElse(key2)[Option[Int]](default1) == default1) + assert(db1.getOrElse(key3)[Option[String]](default1) == default1) + assert(db1.getOrElse(key1)(default1) == Some(4)) + assert(db1.getOrElse(key2)(default1) == default1) + assert(db1.getOrElse(key3)(default1) == default1) + + val default2: Any = 3 + assert(db1.getOrElse(key1)[Any](default2) == Some(4)) + assert(db1.getOrElse(key2)[Any](default2) == default2) + assert(db1.getOrElse(key3)[Any](default2) == default2) + assert(db1.getOrElse(key1)(default2) == Some(4)) + assert(db1.getOrElse(key2)(default2) == default2) + assert(db1.getOrElse(key3)(default2) == default2) + + // Extension method and using parameter + extension (k: Key) + def lookupOrElse(using db: DB)[V >: k.Value](default: V): V = db.getOrElse(k)(default) + + object Block1: + given DB = db1 + + assert(key1.lookupOrElse[Option[Int]](default1) == Some(4)) + assert(key2.lookupOrElse[Option[Int]](default1) == default1) + assert(key3.lookupOrElse[Option[String]](default1) == default1) + assert(key1.lookupOrElse(default1) == Some(4)) + assert(key2.lookupOrElse(default1) == default1) + assert(key3.lookupOrElse(default1) == default1) + + assert(key1.lookupOrElse[Any](default2) == Some(4)) + assert(key2.lookupOrElse[Any](default2) == default2) + assert(key3.lookupOrElse[Any](default2) == default2) + assert(key1.lookupOrElse(default2) == Some(4)) + assert(key2.lookupOrElse(default2) == default2) + assert(key3.lookupOrElse(default2) == default2) + end Block1 + + // Right associative extension method + extension (db: DB) + def ?:(k: Key)[V >: k.Value](default: V): V = db.getOrElse(k)(default) + + assert((db1 ?: (key1))[Option[Int]](default1) == Some(4)) + assert((db1 ?: (key2))[Option[Int]](default1) == default1) + assert((db1 ?: (key3))[Option[String]](default1) == default1) + assert((db1 ?: (key1))(default1) == Some(4)) + assert((db1 ?: (key2))(default1) == default1) + assert((db1 ?: (key3))(default1) == default1) + + assert((db1 ?: (key1))[Any](default2) == Some(4)) + assert((db1 ?: (key2))[Any](default2) == default2) + assert((db1 ?: (key3))[Any](default2) == default2) + assert((db1 ?: (key1))(default2) == Some(4)) + assert((db1 ?: (key2))(default2) == default2) + assert((db1 ?: (key3))(default2) == default2) + + + assert(key1.?:(db1)[Option[Int]](default1) == Some(4)) + assert(key2.?:(db1)[Option[Int]](default1) == default1) + assert(key3.?:(db1)[Option[String]](default1) == default1) + assert(key1.?:(db1)(default1) == Some(4)) + assert(key2.?:(db1)(default1) == default1) + assert(key3.?:(db1)(default1) == default1) + + assert(key1.?:(db1)[Any](default2) == Some(4)) + assert(key2.?:(db1)[Any](default2) == default2) + assert(key3.?:(db1)[Any](default2) == default2) + assert(key1.?:(db1)(default2) == Some(4)) + assert(key2.?:(db1)(default2) == default2) + assert(key3.?:(db1)(default2) == default2) + + + assert(?:(key1)(db1)[Option[Int]](default1) == Some(4)) + assert(?:(key2)(db1)[Option[Int]](default1) == default1) + assert(?:(key3)(db1)[Option[String]](default1) == default1) + assert(?:(key1)(db1)(default1) == Some(4)) + assert(?:(key2)(db1)(default1) == default1) + assert(?:(key3)(db1)(default1) == default1) + + assert(?:(key1)(db1)[Any](default2) == Some(4)) + assert(?:(key2)(db1)[Any](default2) == default2) + assert(?:(key3)(db1)[Any](default2) == default2) + assert(?:(key1)(db1)(default2) == Some(4)) + assert(?:(key2)(db1)(default2) == default2) + assert(?:(key3)(db1)(default2) == default2) +}