From 4e9cdc4afa359553914c217c6455625a5c5c99db Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 18 Apr 2022 22:16:37 +0200 Subject: [PATCH 1/7] Allow exports in extension clauses This is part of a long term strategy to get deprecate and remove general implicit conversions in Scala. In the thread https://contributors.scala-lang.org/t/can-we-wean-scala-off-implicit-conversions/4388 we identified two areas where implicit conversions or implicit classes were still essential. One was adding methods to new types in bulk. This can be achieved by defining an implicit class that inherits from some other classes that define the methods. Exports in extension methods provide a similar functionality without relying on implicit conversions under the covers. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 93 +++-- compiler/src/dotty/tools/dotc/ast/untpd.scala | 4 +- .../dotty/tools/dotc/parsing/Parsers.scala | 45 +- .../src/dotty/tools/dotc/typer/Namer.scala | 104 ++++- .../src/dotty/tools/dotc/typer/ReTyper.scala | 3 + .../src/dotty/tools/dotc/typer/Typer.scala | 3 +- docs/_docs/internals/syntax.md | 375 ++++++++--------- .../reference/other-new-features/export.md | 46 ++- docs/_docs/reference/syntax.md | 391 +++++++++--------- tests/neg/exports.check | 6 + tests/neg/exports.scala | 6 + tests/pos/reference/exports.scala | 24 +- tests/run/export-in-extension.scala | 23 ++ 13 files changed, 652 insertions(+), 471 deletions(-) create mode 100644 tests/run/export-in-extension.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index cb117d286a02..445426ab4118 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -393,6 +393,16 @@ object desugar { vparam.withMods(mods & (GivenOrImplicit | Erased | hasDefault) | Param) } + def mkApply(fn: Tree, paramss: List[ParamClause])(using Context): Tree = + paramss.foldLeft(fn) { (fn, params) => params match + case TypeDefs(params) => + TypeApply(fn, params.map(refOfDef)) + case (vparam: ValDef) :: _ if vparam.mods.is(Given) => + Apply(fn, params.map(refOfDef)).setApplyKind(ApplyKind.Using) + case _ => + Apply(fn, params.map(refOfDef)) + } + /** The expansion of a class definition. See inline comments for what is involved */ def classDef(cdef: TypeDef)(using Context): Tree = { val impl @ Template(constr0, _, self, _) = cdef.rhs @@ -588,7 +598,7 @@ object desugar { } // new C[Ts](paramss) - lazy val creatorExpr = { + lazy val creatorExpr = val vparamss = constrVparamss match case (vparam :: _) :: _ if vparam.mods.is(Implicit) => // add a leading () to match class parameters Nil :: constrVparamss @@ -607,7 +617,6 @@ object desugar { } } ensureApplied(nu) - } val copiedAccessFlags = if migrateTo3 then EmptyFlags else AccessFlags @@ -892,48 +901,50 @@ object desugar { } } + def extMethod(mdef: DefDef, extParamss: List[ParamClause])(using Context): DefDef = + cpy.DefDef(mdef)( + name = normalizeName(mdef, mdef.tpt).asTermName, + paramss = + if mdef.name.isRightAssocOperatorName then + val (typaramss, paramss) = mdef.paramss.span(isTypeParamClause) // first extract type parameters + + paramss match + case params :: paramss1 => // `params` must have a single parameter and without `given` flag + + def badRightAssoc(problem: String) = + report.error(i"right-associative extension method $problem", mdef.srcPos) + extParamss ++ mdef.paramss + + params match + case ValDefs(vparam :: Nil) => + if !vparam.mods.is(Given) then + // we merge the extension parameters with the method parameters, + // swapping the operator arguments: + // e.g. + // extension [A](using B)(c: C)(using D) + // 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 + else + badRightAssoc("cannot start with using clause") + case _ => + badRightAssoc("must start with a single parameter") + case _ => + // no value parameters, so not an infix operator. + extParamss ++ mdef.paramss + else + extParamss ++ mdef.paramss + ).withMods(mdef.mods | ExtensionMethod) + /** Transform extension construct to list of extension methods */ def extMethods(ext: ExtMethods)(using Context): Tree = flatTree { - for mdef <- ext.methods yield - defDef( - cpy.DefDef(mdef)( - name = normalizeName(mdef, ext).asTermName, - paramss = - if mdef.name.isRightAssocOperatorName then - val (typaramss, paramss) = mdef.paramss.span(isTypeParamClause) // first extract type parameters - - paramss match - case params :: paramss1 => // `params` must have a single parameter and without `given` flag - - def badRightAssoc(problem: String) = - report.error(i"right-associative extension method $problem", mdef.srcPos) - ext.paramss ++ mdef.paramss - - params match - case ValDefs(vparam :: Nil) => - if !vparam.mods.is(Given) then - // we merge the extension parameters with the method parameters, - // swapping the operator arguments: - // e.g. - // extension [A](using B)(c: C)(using D) - // 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) = ext.paramss.span(isUsingOrTypeParamClause) - leadingUsing ::: typaramss ::: params :: otherExtParamss ::: paramss1 - else - badRightAssoc("cannot start with using clause") - case _ => - badRightAssoc("must start with a single parameter") - case _ => - // no value parameters, so not an infix operator. - ext.paramss ++ mdef.paramss - else - ext.paramss ++ mdef.paramss - ).withMods(mdef.mods | ExtensionMethod) - ) + ext.methods map { + case exp: Export => exp + case mdef: DefDef => defDef(extMethod(mdef, ext.paramss)) + } } - /** Transforms * * type t >: Low <: Hi diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 365b969faffc..7d417b023c14 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -117,7 +117,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class GenAlias(pat: Tree, expr: Tree)(implicit @constructorOnly src: SourceFile) extends Tree case class ContextBounds(bounds: TypeBoundsTree, cxBounds: List[Tree])(implicit @constructorOnly src: SourceFile) extends TypTree case class PatDef(mods: Modifiers, pats: List[Tree], tpt: Tree, rhs: Tree)(implicit @constructorOnly src: SourceFile) extends DefTree - case class ExtMethods(paramss: List[ParamClause], methods: List[DefDef])(implicit @constructorOnly src: SourceFile) extends Tree + case class ExtMethods(paramss: List[ParamClause], methods: List[Tree])(implicit @constructorOnly src: SourceFile) extends Tree case class MacroTree(expr: Tree)(implicit @constructorOnly src: SourceFile) extends Tree case class ImportSelector(imported: Ident, renamed: Tree = EmptyTree, bound: Tree = EmptyTree)(implicit @constructorOnly src: SourceFile) extends Tree { @@ -640,7 +640,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case tree: PatDef if (mods eq tree.mods) && (pats eq tree.pats) && (tpt eq tree.tpt) && (rhs eq tree.rhs) => tree case _ => finalize(tree, untpd.PatDef(mods, pats, tpt, rhs)(tree.source)) } - def ExtMethods(tree: Tree)(paramss: List[ParamClause], methods: List[DefDef])(using Context): Tree = tree match + def ExtMethods(tree: Tree)(paramss: List[ParamClause], methods: List[Tree])(using Context): Tree = tree match case tree: ExtMethods if (paramss eq tree.paramss) && (methods == tree.methods) => tree case _ => finalize(tree, untpd.ExtMethods(paramss, methods)(tree.source)) def ImportSelector(tree: Tree)(imported: Ident, renamed: Tree, bound: Tree)(using Context): Tree = tree match { diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 643cf8c9caca..6cf1d4884dc6 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3123,7 +3123,7 @@ object Parsers { /** Import ::= `import' ImportExpr {‘,’ ImportExpr} * Export ::= `export' ImportExpr {‘,’ ImportExpr} */ - def importClause(leading: Token, mkTree: ImportConstr): List[Tree] = { + def importOrExportClause(leading: Token, mkTree: ImportConstr): List[Tree] = { val offset = accept(leading) commaSeparated(importExpr(mkTree)) match { case t :: rest => @@ -3136,6 +3136,12 @@ object Parsers { } } + def exportClause() = + importOrExportClause(EXPORT, Export(_,_)) + + def importClause(outermost: Boolean = false) = + importOrExportClause(IMPORT, mkImport(outermost)) + /** Create an import node and handle source version imports */ def mkImport(outermost: Boolean = false): ImportConstr = (tree, selectors) => val imp = Import(tree, selectors) @@ -3685,8 +3691,10 @@ object Parsers { if in.isColon() then syntaxError("no `:` expected here") in.nextToken() - val methods = - if isDefIntro(modifierTokens) then + val methods: List[Tree] = + if in.token == EXPORT then + exportClause() + else if isDefIntro(modifierTokens) then extMethod(nparams) :: Nil else in.observeIndented() @@ -3696,12 +3704,13 @@ object Parsers { val result = atSpan(start)(ExtMethods(joinParams(tparams, leadParamss.toList), methods)) val comment = in.getDocComment(start) if comment.isDefined then - for meth <- methods do + for case meth: DefDef <- methods do if !meth.rawComment.isDefined then meth.setComment(comment) result end extension /** ExtMethod ::= {Annotation [nl]} {Modifier} ‘def’ DefDef + * | Export */ def extMethod(numLeadParams: Int): DefDef = val start = in.offset @@ -3711,16 +3720,18 @@ object Parsers { /** ExtMethods ::= ExtMethod | [nl] ‘{’ ExtMethod {semi ExtMethod ‘}’ */ - def extMethods(numLeadParams: Int): List[DefDef] = checkNoEscapingPlaceholders { - val meths = new ListBuffer[DefDef] + def extMethods(numLeadParams: Int): List[Tree] = checkNoEscapingPlaceholders { + val meths = new ListBuffer[Tree] while val start = in.offset - val mods = defAnnotsMods(modifierTokens) - in.token != EOF && { - accept(DEF) - meths += defDefOrDcl(start, mods, numLeadParams) - in.token != EOF && statSepOrEnd(meths, what = "extension method") - } + if in.token == EXPORT then + meths ++= exportClause() + else + val mods = defAnnotsMods(modifierTokens) + if in.token != EOF then + accept(DEF) + meths += defDefOrDcl(start, mods, numLeadParams) + in.token != EOF && statSepOrEnd(meths, what = "extension method") do () if meths.isEmpty then syntaxErrorOrIncomplete("`def` expected") meths.toList @@ -3868,9 +3879,9 @@ object Parsers { else stats += packaging(start) } else if (in.token == IMPORT) - stats ++= importClause(IMPORT, mkImport(outermost)) + stats ++= importClause(outermost) else if (in.token == EXPORT) - stats ++= importClause(EXPORT, Export(_,_)) + stats ++= exportClause() else if isIdent(nme.extension) && followingIsExtension() then stats += extension() else if isDefIntro(modifierTokens) then @@ -3916,9 +3927,9 @@ object Parsers { while var empty = false if (in.token == IMPORT) - stats ++= importClause(IMPORT, mkImport()) + stats ++= importClause() else if (in.token == EXPORT) - stats ++= importClause(EXPORT, Export(_,_)) + stats ++= exportClause() else if isIdent(nme.extension) && followingIsExtension() then stats += extension() else if (isDefIntro(modifierTokensOrCase)) @@ -3994,7 +4005,7 @@ object Parsers { while var empty = false if (in.token == IMPORT) - stats ++= importClause(IMPORT, mkImport()) + stats ++= importClause() else if (isExprIntro) stats += expr(Location.InBlock) else if in.token == IMPLICIT && !in.inModifierPosition() then diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 07ac87d5455a..9061466fcf14 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1070,16 +1070,26 @@ class Namer { typer: Typer => def init(): Context = index(params) - /** The forwarders defined by export `exp` */ - private def exportForwarders(exp: Export)(using Context): List[tpd.MemberDef] = + /** The forwarders defined by export `exp` + * @param pathMethod If it exists, the symbol referenced by the path of an export + * in an extension clause. That symbol is always a companion + * extension method. + */ + private def exportForwarders(exp: Export, pathMethod: Symbol)(using Context): List[tpd.MemberDef] = val buf = new mutable.ListBuffer[tpd.MemberDef] val Export(expr, selectors) = exp if expr.isEmpty then report.error(em"Export selector must have prefix and `.`", exp.srcPos) return Nil - val path = typedAheadExpr(expr, AnySelectionProto) - checkLegalExportPath(path, selectors) + val (path, pathType) = + if pathMethod.exists then + val path = typedAhead(expr, _.withType(pathMethod.termRef)) + (path, pathMethod.info.finalResultType) + else + val path = typedAheadExpr(expr, AnySelectionProto) + checkLegalExportPath(path, selectors) + (path, path.tpe) lazy val wildcardBound = importBound(selectors, isGiven = false) lazy val givenBound = importBound(selectors, isGiven = true) @@ -1087,7 +1097,7 @@ class Namer { typer: Typer => def canForward(mbr: SingleDenotation, alias: TermName): CanForward = { import CanForward.* val sym = mbr.symbol - if !sym.isAccessibleFrom(path.tpe) then + if !sym.isAccessibleFrom(pathType) then No("is not accessible") else if sym.isConstructor || sym.is(ModuleClass) || sym.is(Bridge) || sym.is(ConstructorProxy) then Skip @@ -1103,7 +1113,10 @@ class Namer { typer: Typer => case None => Yes else if sym.isAllOf(JavaModule) then Skip - else Yes + else if pathMethod.exists && mbr.isType then + No("cannot be exported as extension method") + else + Yes } def foreachDefaultGetterOf(sym: TermSymbol, op: TermSymbol => Unit): Unit = @@ -1112,7 +1125,7 @@ class Namer { typer: Typer => if param.isTerm then if param.is(HasDefault) then val getterName = DefaultGetterName(sym.name, n) - val getter = path.tpe.member(DefaultGetterName(sym.name, n)).symbol + val getter = pathType.member(DefaultGetterName(sym.name, n)).symbol assert(getter.exists, i"$path does not have a default getter named $getterName") op(getter.asTerm) n += 1 @@ -1143,7 +1156,7 @@ class Namer { typer: Typer => val forwarder = if mbr.isType then val forwarderName = checkNoConflict(alias.toTypeName, isPrivate = false, span) - var target = path.tpe.select(sym) + var target = pathType.select(sym) if target.typeParams.nonEmpty then target = target.EtaExpand(target.typeParams) newSymbol( @@ -1160,14 +1173,28 @@ class Namer { typer: Typer => case tp: TermRef => tp.termSymbol.is(Private) || refersToPrivate(tp.prefix) case _ => false val (maybeStable, mbrInfo) = - if sym.isStableMember && sym.isPublic && !refersToPrivate(path.tpe) then - (StableRealizable, ExprType(path.tpe.select(sym))) + if sym.isStableMember + && sym.isPublic + && pathType.isStable + && !refersToPrivate(pathType) + then + (StableRealizable, ExprType(pathType.select(sym))) else - (EmptyFlags, mbr.info.ensureMethodic) + def addPathMethodParams(pt: Type, info: Type): Type = pt match + case pt: MethodOrPoly => + pt.derivedLambdaType(resType = addPathMethodParams(pt.resType, info)) + case _ => + info + val mbrInfo = + if pathMethod.exists + then addPathMethodParams(pathMethod.info, mbr.info.widenExpr) + else mbr.info.ensureMethodic + (EmptyFlags, mbrInfo) var flagMask = RetainedExportFlags if sym.isTerm then flagMask |= HasDefaultParams | NoDefaultParams var mbrFlags = Exported | Method | Final | maybeStable | sym.flags & flagMask - if sym.is(ExtensionMethod) then mbrFlags |= ExtensionMethod + if sym.is(ExtensionMethod) || pathMethod.exists then + mbrFlags |= ExtensionMethod val forwarderName = checkNoConflict(alias, isPrivate = false, span) newSymbol(cls, forwarderName, mbrFlags, mbrInfo, coord = span) @@ -1178,9 +1205,14 @@ class Namer { typer: Typer => buf += tpd.TypeDef(forwarder.asType).withSpan(span) else import tpd._ - val ref = path.select(sym.asTerm) - val ddef = tpd.DefDef(forwarder.asTerm, prefss => - ref.appliedToArgss(adaptForwarderParams(Nil, sym.info, prefss))) + def extensionParamsCount(pt: Type): Int = pt match + case pt: MethodOrPoly => 1 + extensionParamsCount(pt.resType) + case _ => 0 + val ddef = tpd.DefDef(forwarder.asTerm, prefss => { + val (pathRefss, methRefss) = prefss.splitAt(extensionParamsCount(path.tpe.widen)) + val ref = path.appliedToArgss(pathRefss).select(sym.asTerm) + ref.appliedToArgss(adaptForwarderParams(Nil, sym.info, methRefss)) + }) if forwarder.isInlineMethod then PrepareInlineable.registerInlineInfo(forwarder, ddef.rhs) buf += ddef.withSpan(span) @@ -1192,7 +1224,7 @@ class Namer { typer: Typer => def addForwardersNamed(name: TermName, alias: TermName, span: Span): Unit = val size = buf.size - val mbrs = List(name, name.toTypeName).flatMap(path.tpe.member(_).alternatives) + val mbrs = List(name, name.toTypeName).flatMap(pathType.member(_).alternatives) mbrs.foreach(addForwarder(alias, _, span)) targets += alias if buf.size == size then @@ -1203,15 +1235,15 @@ class Namer { typer: Typer => def addWildcardForwardersNamed(name: TermName, span: Span): Unit = List(name, name.toTypeName) - .flatMap(path.tpe.memberBasedOnFlags(_, excluded = Private|Given|ConstructorProxy).alternatives) + .flatMap(pathType.memberBasedOnFlags(_, excluded = Private|Given|ConstructorProxy).alternatives) .foreach(addForwarder(name, _, span)) // ignore if any are not added def addWildcardForwarders(seen: List[TermName], span: Span): Unit = val nonContextual = mutable.HashSet(seen: _*) - val fromCaseClass = path.tpe.widen.classSymbols.exists(_.is(Case)) + val fromCaseClass = pathType.widen.classSymbols.exists(_.is(Case)) def isCaseClassSynthesized(mbr: Symbol) = fromCaseClass && defn.caseClassSynthesized.contains(mbr) - for mbr <- path.tpe.membersBasedOnFlags(required = EmptyFlags, excluded = PrivateOrSynthetic) do + for mbr <- pathType.membersBasedOnFlags(required = EmptyFlags, excluded = PrivateOrSynthetic) do if !mbr.symbol.isSuperAccessor // Scala 2 superaccessors have neither Synthetic nor Artfact set, so we // need to filter them out here (by contrast, Scala 3 superaccessors are Artifacts) @@ -1304,20 +1336,48 @@ class Namer { typer: Typer => /** Add forwarders as required by the export statements in this class */ private def processExports(using Context): Unit = + def processExport(exp: Export, pathSym: Symbol)(using Context): Unit = + for forwarder <- exportForwarders(exp, pathSym) do + forwarder.symbol.entered + + def exportPathSym(path: Tree, ext: ExtMethods)(using Context): Symbol = + def fail(msg: String): Symbol = + report.error(em"export qualifier $msg", path.srcPos) + NoSymbol + path match + case Ident(name) => + def matches(cand: Tree) = cand match + case meth: DefDef => meth.name == name && meth.paramss.hasSameLengthAs(ext.paramss) + case _ => false + expanded(ext).toList.filter(matches) match + case cand :: Nil => symbolOfTree(cand) + case Nil => fail(i"$name is not a parameterless companion extension method") + case _ => fail(i"$name cannot be overloaded") + case _ => + fail("must be a simple reference to a companion extension method") + def process(stats: List[Tree])(using Context): Unit = stats match case (stat: Export) :: stats1 => - for forwarder <- exportForwarders(stat) do - forwarder.symbol.entered + processExport(stat, NoSymbol) process(stats1) case (stat: Import) :: stats1 => process(stats1)(using ctx.importContext(stat, symbolOfTree(stat))) + case (stat: ExtMethods) :: stats1 => + for case exp: Export <- stat.methods do + processExport(exp, exportPathSym(exp.expr, stat)) + process(stats1) case stat :: stats1 => process(stats1) case Nil => + def hasExport(stats: List[Tree]): Boolean = stats.exists { + case _: Export => true + case ExtMethods(_, stats1) => hasExport(stats1) + case _ => false + } // Do a quick scan whether we need to process at all. This avoids creating // import contexts for nothing. - if rest.exists(_.isInstanceOf[Export]) then + if hasExport(rest) then process(rest) end processExports diff --git a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala index 4798563fab43..385d71b570ca 100644 --- a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala @@ -73,6 +73,9 @@ class ReTyper(nestingLevel: Int = 0) extends Typer(nestingLevel) with ReChecking override def typedRefinedTypeTree(tree: untpd.RefinedTypeTree)(using Context): TypTree = promote(TypeTree(tree.tpe).withSpan(tree.span)) + override def typedExport(exp: untpd.Export)(using Context): Export = + promote(exp) + override def typedBind(tree: untpd.Bind, pt: Type)(using Context): Bind = { assertTyped(tree) val body1 = typed(tree.body, pt) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 0af8ee8dc624..26414ffdb461 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2617,8 +2617,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer assignType(cpy.Import(imp)(expr1, selectors1), sym) def typedExport(exp: untpd.Export)(using Context): Export = - val expr1 = typedExpr(exp.expr, AnySelectionProto) - // already called `checkLegalExportPath` in Namer + val expr1 = exp.expr.removeAttachment(TypedAhead).getOrElse(EmptyTree) val selectors1 = typedSelectors(exp.selectors) assignType(cpy.Export(exp)(expr1, selectors1)) diff --git a/docs/_docs/internals/syntax.md b/docs/_docs/internals/syntax.md index 7eba33f29ef1..cae2ebae7af2 100644 --- a/docs/_docs/internals/syntax.md +++ b/docs/_docs/internals/syntax.md @@ -10,8 +10,8 @@ _Unicode escapes_ are used to represent the [Unicode character](https://www.w3.o hexadecimal code: ```ebnf -UnicodeEscape ::= ‘\’ ‘u’ {‘u’} hexDigit hexDigit hexDigit hexDigit ; -hexDigit ::= ‘0’ | … | ‘9’ | ‘A’ | … | ‘F’ | ‘a’ | … | ‘f’ ; +UnicodeEscape ::= ‘\’ ‘u’ {‘u’} hexDigit hexDigit hexDigit hexDigit +hexDigit ::= ‘0’ | … | ‘9’ | ‘A’ | … | ‘F’ | ‘a’ | … | ‘f’ ``` Informal descriptions are typeset as `“some comment”`. @@ -22,69 +22,69 @@ The lexical syntax of Scala is given by the following grammar in EBNF form. ```ebnf -whiteSpace ::= ‘\u0020’ | ‘\u0009’ | ‘\u000D’ | ‘\u000A’ ; -upper ::= ‘A’ | … | ‘Z’ | ‘\$’ | ‘_’ “… and Unicode category Lu” ; -lower ::= ‘a’ | … | ‘z’ “… and Unicode category Ll” ; -letter ::= upper | lower “… and Unicode categories Lo, Lt, Lm, Nl” ; -digit ::= ‘0’ | … | ‘9’ ; -paren ::= ‘(’ | ‘)’ | ‘[’ | ‘]’ | ‘{’ | ‘}’ | ‘'(’ | ‘'[’ | ‘'{’ ; -delim ::= ‘`’ | ‘'’ | ‘"’ | ‘.’ | ‘;’ | ‘,’ ; +whiteSpace ::= ‘\u0020’ | ‘\u0009’ | ‘\u000D’ | ‘\u000A’ +upper ::= ‘A’ | … | ‘Z’ | ‘\$’ | ‘_’ “… and Unicode category Lu” +lower ::= ‘a’ | … | ‘z’ “… and Unicode category Ll” +letter ::= upper | lower “… and Unicode categories Lo, Lt, Lm, Nl” +digit ::= ‘0’ | … | ‘9’ +paren ::= ‘(’ | ‘)’ | ‘[’ | ‘]’ | ‘{’ | ‘}’ | ‘'(’ | ‘'[’ | ‘'{’ +delim ::= ‘`’ | ‘'’ | ‘"’ | ‘.’ | ‘;’ | ‘,’ opchar ::= ‘!’ | ‘#’ | ‘%’ | ‘&’ | ‘*’ | ‘+’ | ‘-’ | ‘/’ | ‘:’ | ‘<’ | ‘=’ | ‘>’ | ‘?’ | ‘@’ | ‘\’ | ‘^’ | ‘|’ | ‘~’ - “… and Unicode categories Sm, So” ; -printableChar ::= “all characters in [\u0020, \u007E] inclusive” ; -charEscapeSeq ::= ‘\’ (‘b’ | ‘t’ | ‘n’ | ‘f’ | ‘r’ | ‘"’ | ‘'’ | ‘\’) ; + “… and Unicode categories Sm, So” +printableChar ::= “all characters in [\u0020, \u007E] inclusive” +charEscapeSeq ::= ‘\’ (‘b’ | ‘t’ | ‘n’ | ‘f’ | ‘r’ | ‘"’ | ‘'’ | ‘\’) -op ::= opchar {opchar} ; -varid ::= lower idrest ; +op ::= opchar {opchar} +varid ::= lower idrest alphaid ::= upper idrest - | varid ; + | varid plainid ::= alphaid - | op ; + | op id ::= plainid - | ‘`’ { charNoBackQuoteOrNewline | UnicodeEscape | charEscapeSeq } ‘`’ ; -idrest ::= {letter | digit} [‘_’ op] ; -quoteId ::= ‘'’ alphaid ; + | ‘`’ { charNoBackQuoteOrNewline | UnicodeEscape | charEscapeSeq } ‘`’ +idrest ::= {letter | digit} [‘_’ op] +quoteId ::= ‘'’ alphaid -integerLiteral ::= (decimalNumeral | hexNumeral) [‘L’ | ‘l’] ; -decimalNumeral ::= ‘0’ | nonZeroDigit [{digit | ‘_’} digit] ; -hexNumeral ::= ‘0’ (‘x’ | ‘X’) hexDigit [{hexDigit | ‘_’} hexDigit] ; -nonZeroDigit ::= ‘1’ | … | ‘9’ ; +integerLiteral ::= (decimalNumeral | hexNumeral) [‘L’ | ‘l’] +decimalNumeral ::= ‘0’ | nonZeroDigit [{digit | ‘_’} digit] +hexNumeral ::= ‘0’ (‘x’ | ‘X’) hexDigit [{hexDigit | ‘_’} hexDigit] +nonZeroDigit ::= ‘1’ | … | ‘9’ floatingPointLiteral ::= [decimalNumeral] ‘.’ digit [{digit | ‘_’} digit] [exponentPart] [floatType] | decimalNumeral exponentPart [floatType] - | decimalNumeral floatType ; -exponentPart ::= (‘E’ | ‘e’) [‘+’ | ‘-’] digit [{digit | ‘_’} digit] ; -floatType ::= ‘F’ | ‘f’ | ‘D’ | ‘d’ ; + | decimalNumeral floatType +exponentPart ::= (‘E’ | ‘e’) [‘+’ | ‘-’] digit [{digit | ‘_’} digit] +floatType ::= ‘F’ | ‘f’ | ‘D’ | ‘d’ -booleanLiteral ::= ‘true’ | ‘false’ ; +booleanLiteral ::= ‘true’ | ‘false’ -characterLiteral ::= ‘'’ (printableChar | charEscapeSeq) ‘'’ ; +characterLiteral ::= ‘'’ (printableChar | charEscapeSeq) ‘'’ stringLiteral ::= ‘"’ {stringElement} ‘"’ - | ‘"""’ multiLineChars ‘"""’ ; + | ‘"""’ multiLineChars ‘"""’ stringElement ::= printableChar \ (‘"’ | ‘\’) | UnicodeEscape - | charEscapeSeq ; -multiLineChars ::= {[‘"’] [‘"’] char \ ‘"’} {‘"’} ; + | charEscapeSeq +multiLineChars ::= {[‘"’] [‘"’] char \ ‘"’} {‘"’} processedStringLiteral ::= alphaid ‘"’ {[‘\’] processedStringPart | ‘\\’ | ‘\"’} ‘"’ - | alphaid ‘"""’ {[‘"’] [‘"’] char \ (‘"’ | ‘$’) | escape} {‘"’} ‘"""’ ; + | alphaid ‘"""’ {[‘"’] [‘"’] char \ (‘"’ | ‘$’) | escape} {‘"’} ‘"""’ processedStringPart - ::= printableChar \ (‘"’ | ‘$’ | ‘\’) | escape ; + ::= printableChar \ (‘"’ | ‘$’ | ‘\’) | escape escape ::= ‘$$’ | ‘$’ letter { letter | digit } - | ‘{’ Block [‘;’ whiteSpace stringFormat whiteSpace] ‘}’ ; -stringFormat ::= {printableChar \ (‘"’ | ‘}’ | ‘ ’ | ‘\t’ | ‘\n’)} ; + | ‘{’ Block [‘;’ whiteSpace stringFormat whiteSpace] ‘}’ +stringFormat ::= {printableChar \ (‘"’ | ‘}’ | ‘ ’ | ‘\t’ | ‘\n’)} -symbolLiteral ::= ‘'’ plainid // until 2.13 ; +symbolLiteral ::= ‘'’ plainid // until 2.13 comment ::= ‘/*’ “any sequence of characters; nested comments are allowed” ‘*/’ - | ‘//’ “any sequence of characters up to end of line” ; + | ‘//’ “any sequence of characters up to end of line” -nl ::= “new line character” ; -semi ::= ‘;’ | nl {nl} ; +nl ::= “new line character” +semi ::= ‘;’ | nl {nl} ``` @@ -100,9 +100,9 @@ a `:` at the end of a line. ``` <<< ts >>> ::= ‘{’ ts ‘}’ - | indent ts outdent ; + | indent ts outdent :<<< ts >>> ::= [nl] ‘{’ ts ‘}’ - | `:` indent ts outdent ; + | `:` indent ts outdent ``` ## Keywords @@ -140,20 +140,20 @@ SimpleLiteral ::= [‘-’] integerLiteral | [‘-’] floatingPointLiteral | booleanLiteral | characterLiteral - | stringLiteral ; + | stringLiteral Literal ::= SimpleLiteral | processedStringLiteral | symbolLiteral - | ‘null’ ; + | ‘null’ -QualId ::= id {‘.’ id} ; -ids ::= id {‘,’ id} ; +QualId ::= id {‘.’ id} +ids ::= id {‘,’ id} SimpleRef ::= id | [id ‘.’] ‘this’ - | [id ‘.’] ‘super’ [ClassQualifier] ‘.’ id ; + | [id ‘.’] ‘super’ [ClassQualifier] ‘.’ id -ClassQualifier ::= ‘[’ id ‘]’ ; +ClassQualifier ::= ‘[’ id ‘]’ ``` ### Types @@ -162,22 +162,22 @@ Type ::= FunType | HkTypeParamClause ‘=>>’ Type LambdaTypeTree(ps, t) | FunParamClause ‘=>>’ Type TermLambdaTypeTree(ps, t) | MatchType - | InfixType ; + | InfixType FunType ::= FunTypeArgs (‘=>’ | ‘?=>’) Type Function(ts, t) - | HKTypeParamClause '=>' Type PolyFunction(ps, t) ; + | HKTypeParamClause '=>' Type PolyFunction(ps, t) FunTypeArgs ::= InfixType | ‘(’ [ FunArgTypes ] ‘)’ - | FunParamClause ; -FunParamClause ::= ‘(’ TypedFunParam {‘,’ TypedFunParam } ‘)’ ; -TypedFunParam ::= id ‘:’ Type ; -MatchType ::= InfixType `match` <<< TypeCaseClauses >>> ; -InfixType ::= RefinedType {id [nl] RefinedType} InfixOp(t1, op, t2) ; -RefinedType ::= AnnotType {[nl] Refinement} RefinedTypeTree(t, ds) ; -AnnotType ::= SimpleType {Annotation} Annotated(t, annot) ; + | FunParamClause +FunParamClause ::= ‘(’ TypedFunParam {‘,’ TypedFunParam } ‘)’ +TypedFunParam ::= id ‘:’ Type +MatchType ::= InfixType `match` <<< TypeCaseClauses >>> +InfixType ::= RefinedType {id [nl] RefinedType} InfixOp(t1, op, t2) +RefinedType ::= AnnotType {[nl] Refinement} RefinedTypeTree(t, ds) +AnnotType ::= SimpleType {Annotation} Annotated(t, annot) SimpleType ::= SimpleLiteral SingletonTypeTree(l) | ‘?’ TypeBounds - | SimpleType1 ; + | SimpleType1 SimpleType1 ::= id Ident(name) | Singleton ‘.’ id Select(t, name) | Singleton ‘.’ ‘type’ SingletonTypeTree(p) @@ -186,34 +186,34 @@ SimpleType1 ::= id | ‘$’ ‘{’ Block ‘}’ -- unless inside quoted pattern | ‘$’ ‘{’ Pattern ‘}’ -- only inside quoted pattern | SimpleType1 TypeArgs AppliedTypeTree(t, args) - | SimpleType1 ‘#’ id Select(t, name) ; + | SimpleType1 ‘#’ id Select(t, name) Singleton ::= SimpleRef | SimpleLiteral - | Singleton ‘.’ id ; -Singletons ::= Singleton { ‘,’ Singleton } ; + | Singleton ‘.’ id +Singletons ::= Singleton { ‘,’ Singleton } FunArgType ::= Type - | ‘=>’ Type PrefixOp(=>, t) ; -FunArgTypes ::= FunArgType { ‘,’ FunArgType } ; -ParamType ::= [‘=>’] ParamValueType ; -ParamValueType ::= Type [‘*’] PostfixOp(t, "*") ; -TypeArgs ::= ‘[’ Types ‘]’ ts ; -Refinement ::= ‘{’ [RefineDcl] {semi [RefineDcl]} ‘}’ ds ; -TypeBounds ::= [‘>:’ Type] [‘<:’ Type] TypeBoundsTree(lo, hi) ; -TypeParamBounds ::= TypeBounds {‘:’ Type} ContextBounds(typeBounds, tps) ; -Types ::= Type {‘,’ Type} ; + | ‘=>’ Type PrefixOp(=>, t) +FunArgTypes ::= FunArgType { ‘,’ FunArgType } +ParamType ::= [‘=>’] ParamValueType +ParamValueType ::= Type [‘*’] PostfixOp(t, "*") +TypeArgs ::= ‘[’ Types ‘]’ ts +Refinement ::= ‘{’ [RefineDcl] {semi [RefineDcl]} ‘}’ ds +TypeBounds ::= [‘>:’ Type] [‘<:’ Type] TypeBoundsTree(lo, hi) +TypeParamBounds ::= TypeBounds {‘:’ Type} ContextBounds(typeBounds, tps) +Types ::= Type {‘,’ Type} ``` ### Expressions ```ebnf Expr ::= FunParams (‘=>’ | ‘?=>’) Expr Function(args, expr), Function(ValDef([implicit], id, TypeTree(), EmptyTree), expr) | HkTypeParamClause ‘=>’ Expr PolyFunction(ts, expr) - | Expr1 ; + | Expr1 BlockResult ::= FunParams (‘=>’ | ‘?=>’) Block | HkTypeParamClause ‘=>’ Block - | Expr1 ; + | Expr1 FunParams ::= Bindings | id - | ‘_’ ; + | ‘_’ Expr1 ::= [‘inline’] ‘if’ ‘(’ Expr ‘)’ {nl} Expr [[semi] ‘else’ Expr] If(Parens(cond), thenp, elsep?) | [‘inline’] ‘if’ Expr ‘then’ Expr [[semi] ‘else’ Expr] If(cond, thenp, elsep?) | ‘while’ ‘(’ Expr ‘)’ {nl} Expr WhileDo(Parens(cond), body) @@ -227,18 +227,18 @@ Expr1 ::= [‘inline’] ‘if’ ‘(’ Expr ‘)’ {nl} Expr [[ | PrefixOperator SimpleExpr ‘=’ Expr Assign(expr, expr) | SimpleExpr ArgumentExprs ‘=’ Expr Assign(expr, expr) | PostfixExpr [Ascription] - | ‘inline’ InfixExpr MatchClause ; + | ‘inline’ InfixExpr MatchClause Ascription ::= ‘:’ InfixType Typed(expr, tp) - | ‘:’ Annotation {Annotation} Typed(expr, Annotated(EmptyTree, annot)*) ; -Catches ::= ‘catch’ (Expr | ExprCaseClause) ; -PostfixExpr ::= InfixExpr [id] PostfixOp(expr, op) ; + | ‘:’ Annotation {Annotation} Typed(expr, Annotated(EmptyTree, annot)*) +Catches ::= ‘catch’ (Expr | ExprCaseClause) +PostfixExpr ::= InfixExpr [id] PostfixOp(expr, op) InfixExpr ::= PrefixExpr | InfixExpr id [nl] InfixExpr InfixOp(expr, op, expr) | InfixExpr id ‘:’ IndentedExpr - | InfixExpr MatchClause ; -MatchClause ::= ‘match’ <<< CaseClauses >>> Match(expr, cases) ; -PrefixExpr ::= [PrefixOperator] SimpleExpr PrefixOp(expr, op) ; -PrefixOperator ::= ‘-’ | ‘+’ | ‘~’ | ‘!’ ; + | InfixExpr MatchClause +MatchClause ::= ‘match’ <<< CaseClauses >>> Match(expr, cases) +PrefixExpr ::= [PrefixOperator] SimpleExpr PrefixOp(expr, op) +PrefixOperator ::= ‘-’ | ‘+’ | ‘~’ | ‘!’ SimpleExpr ::= SimpleRef | Literal | ‘_’ @@ -257,180 +257,181 @@ SimpleExpr ::= SimpleRef | SimpleExpr ‘:’ IndentedExpr -- under language.experimental.fewerBraces | SimpleExpr FunParams (‘=>’ | ‘?=>’) IndentedExpr -- under language.experimental.fewerBraces | SimpleExpr ‘_’ PostfixOp(expr, _) (to be dropped) - | XmlExpr -- to be dropped ; -IndentedExpr ::= indent CaseClauses | Block outdent ; -Quoted ::= ‘'’ ‘{’ Block ‘}’ - | ‘'’ ‘[’ Type ‘]’ ; -ExprsInParens ::= ExprInParens {‘,’ ExprInParens} ; -ExprInParens ::= PostfixExpr ‘:’ Type -- normal Expr allows only RefinedType here ; + | XmlExpr -- to be dropped +IndentedExpr ::= indent CaseClauses | Block outdent +Quoted ::= ‘'’ ‘{’ Block ‘}’ + | ‘'’ ‘[’ Type ‘]’ +ExprsInParens ::= ExprInParens {‘,’ ExprInParens} +ExprInParens ::= PostfixExpr ‘:’ Type -- normal Expr allows only RefinedType here | Expr ParArgumentExprs ::= ‘(’ [‘using’] ExprsInParens ‘)’ exprs - | ‘(’ [ExprsInParens ‘,’] PostfixExpr ‘*’ ‘)’ exprs :+ Typed(expr, Ident(wildcardStar)) ; + | ‘(’ [ExprsInParens ‘,’] PostfixExpr ‘*’ ‘)’ exprs :+ Typed(expr, Ident(wildcardStar)) ArgumentExprs ::= ParArgumentExprs - | BlockExpr ; -BlockExpr ::= <<< CaseClauses | Block >>> ; -Block ::= {BlockStat semi} [BlockResult] Block(stats, expr?) ; + | BlockExpr +BlockExpr ::= <<< CaseClauses | Block >>> +Block ::= {BlockStat semi} [BlockResult] Block(stats, expr?) BlockStat ::= Import | {Annotation {nl}} {LocalModifier} Def | Extension | Expr1 - | EndMarker ; + | EndMarker ForExpr ::= ‘for’ ‘(’ Enumerators0 ‘)’ {nl} [‘do‘ | ‘yield’] Expr ForYield(enums, expr) / ForDo(enums, expr) | ‘for’ ‘{’ Enumerators0 ‘}’ {nl} [‘do‘ | ‘yield’] Expr - | ‘for’ Enumerators0 (‘do‘ | ‘yield’) Expr ; -Enumerators0 ::= {nl} Enumerators [semi] ; -Enumerators ::= Generator {semi Enumerator | Guard} ; + | ‘for’ Enumerators0 (‘do‘ | ‘yield’) Expr +Enumerators0 ::= {nl} Enumerators [semi] +Enumerators ::= Generator {semi Enumerator | Guard} Enumerator ::= Generator | Guard {Guard} - | Pattern1 ‘=’ Expr GenAlias(pat, expr) ; -Generator ::= [‘case’] Pattern1 ‘<-’ Expr GenFrom(pat, expr) ; -Guard ::= ‘if’ PostfixExpr ; - -CaseClauses ::= CaseClause { CaseClause } Match(EmptyTree, cases) ; -CaseClause ::= ‘case’ Pattern [Guard] ‘=>’ Block CaseDef(pat, guard?, block) // block starts at => ; -ExprCaseClause ::= ‘case’ Pattern [Guard] ‘=>’ Expr ; -TypeCaseClauses ::= TypeCaseClause { TypeCaseClause } ; -TypeCaseClause ::= ‘case’ (InfixType | ‘_’) ‘=>’ Type [semi] ; - -Pattern ::= Pattern1 { ‘|’ Pattern1 } Alternative(pats) ; -Pattern1 ::= Pattern2 [‘:’ RefinedType] Bind(name, Typed(Ident(wildcard), tpe)) ; -Pattern2 ::= [id ‘@’] InfixPattern Bind(name, pat) ; -InfixPattern ::= SimplePattern { id [nl] SimplePattern } InfixOp(pat, op, pat) ; + | Pattern1 ‘=’ Expr GenAlias(pat, expr) +Generator ::= [‘case’] Pattern1 ‘<-’ Expr GenFrom(pat, expr) +Guard ::= ‘if’ PostfixExpr + +CaseClauses ::= CaseClause { CaseClause } Match(EmptyTree, cases) +CaseClause ::= ‘case’ Pattern [Guard] ‘=>’ Block CaseDef(pat, guard?, block) // block starts at => +ExprCaseClause ::= ‘case’ Pattern [Guard] ‘=>’ Expr +TypeCaseClauses ::= TypeCaseClause { TypeCaseClause } +TypeCaseClause ::= ‘case’ (InfixType | ‘_’) ‘=>’ Type [semi] + +Pattern ::= Pattern1 { ‘|’ Pattern1 } Alternative(pats) +Pattern1 ::= Pattern2 [‘:’ RefinedType] Bind(name, Typed(Ident(wildcard), tpe)) +Pattern2 ::= [id ‘@’] InfixPattern Bind(name, pat) +InfixPattern ::= SimplePattern { id [nl] SimplePattern } InfixOp(pat, op, pat) SimplePattern ::= PatVar Ident(wildcard) | Literal Bind(name, Ident(wildcard)) | ‘(’ [Patterns] ‘)’ Parens(pats) Tuple(pats) | Quoted | XmlPattern (to be dropped) | SimplePattern1 [TypeArgs] [ArgumentPatterns] - | ‘given’ RefinedType ; + | ‘given’ RefinedType SimplePattern1 ::= SimpleRef - | SimplePattern1 ‘.’ id ; + | SimplePattern1 ‘.’ id PatVar ::= varid - | ‘_’ ; -Patterns ::= Pattern {‘,’ Pattern} ; + | ‘_’ +Patterns ::= Pattern {‘,’ Pattern} ArgumentPatterns ::= ‘(’ [Patterns] ‘)’ Apply(fn, pats) - | ‘(’ [Patterns ‘,’] PatVar ‘*’ ‘)’ ; + | ‘(’ [Patterns ‘,’] PatVar ‘*’ ‘)’ ``` ### Type and Value Parameters ```ebnf -ClsTypeParamClause::= ‘[’ ClsTypeParam {‘,’ ClsTypeParam} ‘]’ ; +ClsTypeParamClause::= ‘[’ ClsTypeParam {‘,’ ClsTypeParam} ‘]’ ClsTypeParam ::= {Annotation} [‘+’ | ‘-’] TypeDef(Modifiers, name, tparams, bounds) - id [HkTypeParamClause] TypeParamBounds Bound(below, above, context) ; + id [HkTypeParamClause] TypeParamBounds Bound(below, above, context) -DefTypeParamClause::= ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’ ; -DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeParamBounds ; +DefTypeParamClause::= ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’ +DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeParamBounds -TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’ ; -TypTypeParam ::= {Annotation} id [HkTypeParamClause] TypeBounds ; +TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’ +TypTypeParam ::= {Annotation} id [HkTypeParamClause] TypeBounds -HkTypeParamClause ::= ‘[’ HkTypeParam {‘,’ HkTypeParam} ‘]’ ; +HkTypeParamClause ::= ‘[’ HkTypeParam {‘,’ HkTypeParam} ‘]’ HkTypeParam ::= {Annotation} [‘+’ | ‘-’] (id [HkTypeParamClause] | ‘_’) - TypeBounds ; + TypeBounds -ClsParamClauses ::= {ClsParamClause} [[nl] ‘(’ [‘implicit’] ClsParams ‘)’] ; +ClsParamClauses ::= {ClsParamClause} [[nl] ‘(’ [‘implicit’] ClsParams ‘)’] ClsParamClause ::= [nl] ‘(’ ClsParams ‘)’ - | [nl] ‘(’ ‘using’ (ClsParams | FunArgTypes) ‘)’ ; -ClsParams ::= ClsParam {‘,’ ClsParam} ; + | [nl] ‘(’ ‘using’ (ClsParams | FunArgTypes) ‘)’ +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. ; + [{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. ``` ### Bindings and Imports ```ebnf -Bindings ::= ‘(’ [Binding {‘,’ Binding}] ‘)’ ; -Binding ::= (id | ‘_’) [‘:’ Type] ValDef(_, id, tpe, EmptyTree) ; +Bindings ::= ‘(’ [Binding {‘,’ Binding}] ‘)’ +Binding ::= (id | ‘_’) [‘:’ Type] ValDef(_, id, tpe, EmptyTree) Modifier ::= LocalModifier | AccessModifier | ‘override’ - | ‘opaque’ ; + | ‘opaque’ LocalModifier ::= ‘abstract’ | ‘final’ | ‘sealed’ | ‘open’ | ‘implicit’ | ‘lazy’ - | ‘inline’ ; -AccessModifier ::= (‘private’ | ‘protected’) [AccessQualifier] ; -AccessQualifier ::= ‘[’ id ‘]’ ; + | ‘inline’ +AccessModifier ::= (‘private’ | ‘protected’) [AccessQualifier] +AccessQualifier ::= ‘[’ id ‘]’ -Annotation ::= ‘@’ SimpleType1 {ParArgumentExprs} Apply(tpe, args) ; +Annotation ::= ‘@’ SimpleType1 {ParArgumentExprs} Apply(tpe, args) -Import ::= ‘import’ ImportExpr {‘,’ ImportExpr} ; -Export ::= ‘export’ ImportExpr {‘,’ ImportExpr} ; +Import ::= ‘import’ ImportExpr {‘,’ ImportExpr} +Export ::= ‘export’ ImportExpr {‘,’ ImportExpr} ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec Import(expr, sels) - | SimpleRef ‘as’ id Import(EmptyTree, ImportSelector(ref, id)) ; + | SimpleRef ‘as’ id Import(EmptyTree, ImportSelector(ref, id)) ImportSpec ::= NamedSelector | WildcardSelector - | ‘{’ ImportSelectors) ‘}’ ; -NamedSelector ::= id [‘as’ (id | ‘_’)] ; -WildCardSelector ::= ‘*' | ‘given’ [InfixType] ; + | ‘{’ ImportSelectors) ‘}’ +NamedSelector ::= id [‘as’ (id | ‘_’)] +WildCardSelector ::= ‘*' | ‘given’ [InfixType] ImportSelectors ::= NamedSelector [‘,’ ImportSelectors] - | WildCardSelector {‘,’ WildCardSelector} ; + | WildCardSelector {‘,’ WildCardSelector} -EndMarker ::= ‘end’ EndMarkerTag -- when followed by EOL ; +EndMarker ::= ‘end’ EndMarkerTag -- when followed by EOL EndMarkerTag ::= id | ‘if’ | ‘while’ | ‘for’ | ‘match’ | ‘try’ - | ‘new’ | ‘this’ | ‘given’ | ‘extension’ | ‘val’ ; + | ‘new’ | ‘this’ | ‘given’ | ‘extension’ | ‘val’ ``` ### Declarations and Definitions ```ebnf RefineDcl ::= ‘val’ ValDcl | ‘def’ DefDcl - | ‘type’ {nl} TypeDcl ; + | ‘type’ {nl} TypeDcl 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 ; + | ‘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 TypeDcl ::= id [TypeParamClause] {FunParamClause} TypeBounds TypeDefTree(_, name, tparams, bound - [‘=’ Type] ; + [‘=’ Type] Def ::= ‘val’ PatDef | ‘var’ PatDef | ‘def’ DefDef | ‘type’ {nl} TypeDcl - | TmplDef ; + | TmplDef PatDef ::= ids [‘:’ Type] ‘=’ Expr - | Pattern2 [‘:’ Type] ‘=’ Expr PatDef(_, pats, tpe?, 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) ; + | ‘this’ DefParamClause DefParamClauses ‘=’ ConstrExpr DefDef(_, , Nil, vparamss, EmptyTree, expr | Block) TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef | [‘case’] ‘object’ ObjectDef | ‘enum’ EnumDef - | ‘given’ GivenDef ; -ClassDef ::= id ClassConstr [Template] ClassDef(mods, name, tparams, templ) ; -ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses with DefDef(_, , Nil, vparamss, EmptyTree, EmptyTree) as first stat ; -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 ; -StructuralInstance ::= ConstrApp {‘with’ ConstrApp} [‘with’ TemplateBody] ; + | ‘given’ GivenDef +ClassDef ::= id ClassConstr [Template] ClassDef(mods, name, tparams, templ) +ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses with DefDef(_, , Nil, vparamss, EmptyTree, EmptyTree) as first stat +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 +StructuralInstance ::= ConstrApp {‘with’ ConstrApp} [‘with’ TemplateBody] Extension ::= ‘extension’ [DefTypeParamClause] {UsingParamClause} - ‘(’ DefParam ‘)’ {UsingParamClause} ExtMethods ; -ExtMethods ::= ExtMethod | [nl] <<< ExtMethod {semi ExtMethod} >>> ; -ExtMethod ::= {Annotation [nl]} {Modifier} ‘def’ DefDef ; -Template ::= InheritClauses [TemplateBody] ; -InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}] ; -ConstrApps ::= ConstrApp ({‘,’ ConstrApp} | {‘with’ ConstrApp}) ; -ConstrApp ::= SimpleType1 {Annotation} {ParArgumentExprs} ; + ‘(’ DefParam ‘)’ {UsingParamClause} ExtMethods +ExtMethods ::= ExtMethod | [nl] <<< ExtMethod {semi ExtMethod} >>> +ExtMethod ::= {Annotation [nl]} {Modifier} ‘def’ DefDef + | Export +Template ::= InheritClauses [TemplateBody] +InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}] +ConstrApps ::= ConstrApp ({‘,’ ConstrApp} | {‘with’ ConstrApp}) +ConstrApp ::= SimpleType1 {Annotation} {ParArgumentExprs} ConstrExpr ::= SelfInvocation - | <<< SelfInvocation {semi BlockStat} >>> ; -SelfInvocation ::= ‘this’ ArgumentExprs {ArgumentExprs} ; + | <<< SelfInvocation {semi BlockStat} >>> +SelfInvocation ::= ‘this’ ArgumentExprs {ArgumentExprs} -TemplateBody ::= :<<< [SelfType] TemplateStat {semi TemplateStat} >>> ; +TemplateBody ::= :<<< [SelfType] TemplateStat {semi TemplateStat} >>> TemplateStat ::= Import | Export | {Annotation [nl]} {Modifier} Def @@ -438,16 +439,16 @@ TemplateStat ::= Import | Extension | Expr1 | EndMarker - | ; + | SelfType ::= id [‘:’ InfixType] ‘=>’ ValDef(_, name, tpt, _) - | ‘this’ ‘:’ InfixType ‘=>’ ; + | ‘this’ ‘:’ InfixType ‘=>’ -EnumBody ::= :<<< [SelfType] EnumStat {semi EnumStat} >>> ; +EnumBody ::= :<<< [SelfType] EnumStat {semi EnumStat} >>> EnumStat ::= TemplateStat - | {Annotation [nl]} {Modifier} EnumCase ; -EnumCase ::= ‘case’ (id ClassConstr [‘extends’ ConstrApps]] | ids) ; + | {Annotation [nl]} {Modifier} EnumCase +EnumCase ::= ‘case’ (id ClassConstr [‘extends’ ConstrApps]] | ids) -TopStats ::= TopStat {semi TopStat} ; +TopStats ::= TopStat {semi TopStat} TopStat ::= Import | Export | {Annotation [nl]} {Modifier} Def @@ -455,9 +456,9 @@ TopStat ::= Import | Packaging | PackageObject | EndMarker - | ; -Packaging ::= ‘package’ QualId :<<< TopStats >>> ; -PackageObject ::= ‘package’ ‘object’ ObjectDef ; + | +Packaging ::= ‘package’ QualId :<<< TopStats >>> +PackageObject ::= ‘package’ ‘object’ ObjectDef -CompilationUnit ::= {‘package’ QualId semi} TopStats ; +CompilationUnit ::= {‘package’ QualId semi} TopStats ``` diff --git a/docs/_docs/reference/other-new-features/export.md b/docs/_docs/reference/other-new-features/export.md index 85f03de4104e..bd2d42e653a1 100644 --- a/docs/_docs/reference/other-new-features/export.md +++ b/docs/_docs/reference/other-new-features/export.md @@ -75,9 +75,23 @@ A member is _eligible_ if all of the following holds: - it is not a constructor, nor the (synthetic) class part of an object, - it is a given instance (declared with `given`) if and only if the export is from a _given selector_. +It is a compile-time error if a simple or renaming selector does not identify +any eligible members. + It is a compile-time error if a simple or renaming selector does not identify any eligible members. -Type members are aliased by type definitions, and term members are aliased by method definitions. Export aliases copy the type and value parameters of the members they refer to. +Type members are aliased by type definitions, and term members are aliased by method definitions. For instance: +```scala +object O: + class C(val x: Int) + def m(c: C): Int = c.x + 1 +export O.* + // generates + // type C = O.C + // def m(c: O.C): Int = O.m(c) +``` + +Export aliases copy the type and value parameters of the members they refer to. Export aliases are always `final`. Aliases of given instances are again defined as givens (and aliases of old-style implicits are `implicit`). Aliases of extensions are again defined as extensions. Aliases of inline methods or values are again defined `inline`. There are no other modifiers that can be given to an alias. This has the following consequences for overriding: - Export aliases cannot be overridden, since they are final. @@ -132,6 +146,34 @@ Export clauses also fill a gap opened by the shift from package objects to top-l of internal compositions available to users of a package. Top-level definitions are not wrapped in a user-defined object, so they can't inherit anything. However, top-level definitions can be export clauses, which supports the facade design pattern in a safer and more flexible way. +## Export Clauses in Extensions + +An export clause may also appear in an extension. + +Example: +```scala +class StringOps(x: String): + def *(n: Int): String = ... + def capitalize: String = ... + +extension (x: String) + def take(n: Int): String = x.substring(0, n) + def drop(n: Int): String = x.substring(n) + private def moreOps = new StringOps(x) + export moreOps.* +``` +In this case the qualifier expression must be an identifier that refers to a unique parameterless extension method in the same extension clause. The export will create +extension methods for all accessible term members +in the result of the qualifier path. For instance, the extension above would be expanded to +```scala +extension (x: String) + def take(n: Int): String = x.substring(0, n) + def drop(n: Int): String = x.substring(n) + private def moreOps = StringOps(x) + def *(n: Int): String = moreOps.*(n) + def capitalize: String = moreOps.capitalize +``` + ## Syntax changes: ``` @@ -139,6 +181,8 @@ TemplateStat ::= ... | Export TopStat ::= ... | Export +ExtMethod ::= ... + | Export Export ::= ‘export’ ImportExpr {‘,’ ImportExpr} ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec ImportSpec ::= NamedSelector diff --git a/docs/_docs/reference/syntax.md b/docs/_docs/reference/syntax.md index 57b21659b7c4..d04a7a848062 100644 --- a/docs/_docs/reference/syntax.md +++ b/docs/_docs/reference/syntax.md @@ -10,9 +10,9 @@ referring to the ASCII fragment `\u0000` – `\u007F`. _Unicode escapes_ are used to represent the [Unicode character](https://www.w3.org/International/articles/definitions-characters/) with the given hexadecimal code: -```ebnf -UnicodeEscape ::= ‘\’ ‘u’ {‘u’} hexDigit hexDigit hexDigit hexDigit ; -hexDigit ::= ‘0’ | … | ‘9’ | ‘A’ | … | ‘F’ | ‘a’ | … | ‘f’ ; +``` +UnicodeEscape ::= ‘\’ ‘u’ {‘u’} hexDigit hexDigit hexDigit hexDigit +hexDigit ::= ‘0’ | … | ‘9’ | ‘A’ | … | ‘F’ | ‘a’ | … | ‘f’ ``` Informal descriptions are typeset as `“some comment”`. @@ -22,70 +22,70 @@ Informal descriptions are typeset as `“some comment”`. The lexical syntax of Scala is given by the following grammar in EBNF form. -```ebnf -whiteSpace ::= ‘\u0020’ | ‘\u0009’ | ‘\u000D’ | ‘\u000A’ ; -upper ::= ‘A’ | … | ‘Z’ | ‘\$’ | ‘_’ “… and Unicode category Lu” ; -lower ::= ‘a’ | … | ‘z’ “… and Unicode category Ll” ; -letter ::= upper | lower “… and Unicode categories Lo, Lt, Nl” ; -digit ::= ‘0’ | … | ‘9’ ; -paren ::= ‘(’ | ‘)’ | ‘[’ | ‘]’ | ‘{’ | ‘}’ | ‘'(’ | ‘'[’ | ‘'{’ ; -delim ::= ‘`’ | ‘'’ | ‘"’ | ‘.’ | ‘;’ | ‘,’ ; +``` +whiteSpace ::= ‘\u0020’ | ‘\u0009’ | ‘\u000D’ | ‘\u000A’ +upper ::= ‘A’ | … | ‘Z’ | ‘\$’ | ‘_’ “… and Unicode category Lu” +lower ::= ‘a’ | … | ‘z’ “… and Unicode category Ll” +letter ::= upper | lower “… and Unicode categories Lo, Lt, Nl” +digit ::= ‘0’ | … | ‘9’ +paren ::= ‘(’ | ‘)’ | ‘[’ | ‘]’ | ‘{’ | ‘}’ | ‘'(’ | ‘'[’ | ‘'{’ +delim ::= ‘`’ | ‘'’ | ‘"’ | ‘.’ | ‘;’ | ‘,’ opchar ::= “printableChar not matched by (whiteSpace | upper | lower | letter | digit | paren | delim | opchar | - Unicode_Sm | Unicode_So)” ; -printableChar ::= “all characters in [\u0020, \u007F] inclusive” ; -charEscapeSeq ::= ‘\’ (‘b’ | ‘t’ | ‘n’ | ‘f’ | ‘r’ | ‘"’ | ‘'’ | ‘\’) ; + Unicode_Sm | Unicode_So)” +printableChar ::= “all characters in [\u0020, \u007F] inclusive” +charEscapeSeq ::= ‘\’ (‘b’ | ‘t’ | ‘n’ | ‘f’ | ‘r’ | ‘"’ | ‘'’ | ‘\’) -op ::= opchar {opchar} ; -varid ::= lower idrest ; +op ::= opchar {opchar} +varid ::= lower idrest alphaid ::= upper idrest - | varid ; + | varid plainid ::= alphaid - | op ; + | op id ::= plainid - | ‘`’ { charNoBackQuoteOrNewline | UnicodeEscape | charEscapeSeq } ‘`’ ; -idrest ::= {letter | digit} [‘_’ op] ; -quoteId ::= ‘'’ alphaid ; + | ‘`’ { charNoBackQuoteOrNewline | UnicodeEscape | charEscapeSeq } ‘`’ +idrest ::= {letter | digit} [‘_’ op] +quoteId ::= ‘'’ alphaid -integerLiteral ::= (decimalNumeral | hexNumeral) [‘L’ | ‘l’] ; -decimalNumeral ::= ‘0’ | nonZeroDigit [{digit | ‘_’} digit] ; -hexNumeral ::= ‘0’ (‘x’ | ‘X’) hexDigit [{hexDigit | ‘_’} hexDigit] ; -nonZeroDigit ::= ‘1’ | … | ‘9’ ; +integerLiteral ::= (decimalNumeral | hexNumeral) [‘L’ | ‘l’] +decimalNumeral ::= ‘0’ | nonZeroDigit [{digit | ‘_’} digit] +hexNumeral ::= ‘0’ (‘x’ | ‘X’) hexDigit [{hexDigit | ‘_’} hexDigit] +nonZeroDigit ::= ‘1’ | … | ‘9’ floatingPointLiteral ::= [decimalNumeral] ‘.’ digit [{digit | ‘_’} digit] [exponentPart] [floatType] | decimalNumeral exponentPart [floatType] - | decimalNumeral floatType ; -exponentPart ::= (‘E’ | ‘e’) [‘+’ | ‘-’] digit [{digit | ‘_’} digit] ; -floatType ::= ‘F’ | ‘f’ | ‘D’ | ‘d’ ; + | decimalNumeral floatType +exponentPart ::= (‘E’ | ‘e’) [‘+’ | ‘-’] digit [{digit | ‘_’} digit] +floatType ::= ‘F’ | ‘f’ | ‘D’ | ‘d’ -booleanLiteral ::= ‘true’ | ‘false’ ; +booleanLiteral ::= ‘true’ | ‘false’ -characterLiteral ::= ‘'’ (printableChar | charEscapeSeq) ‘'’ ; +characterLiteral ::= ‘'’ (printableChar | charEscapeSeq) ‘'’ stringLiteral ::= ‘"’ {stringElement} ‘"’ - | ‘"""’ multiLineChars ‘"""’ ; + | ‘"""’ multiLineChars ‘"""’ stringElement ::= printableChar \ (‘"’ | ‘\’) | UnicodeEscape - | charEscapeSeq ; -multiLineChars ::= {[‘"’] [‘"’] char \ ‘"’} {‘"’} ; + | charEscapeSeq +multiLineChars ::= {[‘"’] [‘"’] char \ ‘"’} {‘"’} processedStringLiteral ::= alphaid ‘"’ {[‘\’] processedStringPart | ‘\\’ | ‘\"’} ‘"’ - | alphaid ‘"""’ {[‘"’] [‘"’] char \ (‘"’ | ‘$’) | escape} {‘"’} ‘"""’ ; + | alphaid ‘"""’ {[‘"’] [‘"’] char \ (‘"’ | ‘$’) | escape} {‘"’} ‘"""’ processedStringPart - ::= printableChar \ (‘"’ | ‘$’ | ‘\’) | escape ; + ::= printableChar \ (‘"’ | ‘$’ | ‘\’) | escape escape ::= ‘$$’ | ‘$’ letter { letter | digit } - | ‘{’ Block [‘;’ whiteSpace stringFormat whiteSpace] ‘}’ ; -stringFormat ::= {printableChar \ (‘"’ | ‘}’ | ‘ ’ | ‘\t’ | ‘\n’)} ; + | ‘{’ Block [‘;’ whiteSpace stringFormat whiteSpace] ‘}’ +stringFormat ::= {printableChar \ (‘"’ | ‘}’ | ‘ ’ | ‘\t’ | ‘\n’)} -symbolLiteral ::= ‘'’ plainid // until 2.13 ; +symbolLiteral ::= ‘'’ plainid // until 2.13 comment ::= ‘/*’ “any sequence of characters; nested comments are allowed” ‘*/’ - | ‘//’ “any sequence of characters up to end of line” ; + | ‘//’ “any sequence of characters up to end of line” -nl ::= “new line character” ; -semi ::= ‘;’ | nl {nl} ; +nl ::= “new line character” +semi ::= ‘;’ | nl {nl} ``` ## Optional Braces @@ -97,31 +97,31 @@ to indicate a token sequence `ts` that is either enclosed in a pair of braces `{ notation `:<<< ts >>>` indicates a token sequence `ts` that is either enclosed in a pair of braces `{ ts }` or that constitutes an indented region `indent ts outdent` that follows a `:` at the end of a line. -```ebnf +``` <<< ts >>> ::= ‘{’ ts ‘}’ - | indent ts outdent ; + | indent ts outdent :<<< ts >>> ::= [nl] ‘{’ ts ‘}’ - | `:` indent ts outdent ; + | `:` indent ts outdent ``` ## Keywords ### Regular keywords -```ebnf +``` abstract case catch class def do else enum export extends false final finally for given if implicit import lazy match new null object override package private protected return sealed super then throw trait true try type val var while with yield -: = <- => <: >: # +: = <- => <: :> # @ =>> ?=> ``` ### Soft keywords -```ebnf +``` as derives end extension infix inline opaque open throws transparent using | * + - ``` @@ -135,45 +135,45 @@ The context-free syntax of Scala is given by the following EBNF grammar: ### Literals and Paths -```ebnf +``` SimpleLiteral ::= [‘-’] integerLiteral | [‘-’] floatingPointLiteral | booleanLiteral | characterLiteral - | stringLiteral ; + | stringLiteral Literal ::= SimpleLiteral | processedStringLiteral | symbolLiteral - | ‘null’ ; + | ‘null’ -QualId ::= id {‘.’ id} ; -ids ::= id {‘,’ id} ; +QualId ::= id {‘.’ id} +ids ::= id {‘,’ id} SimpleRef ::= id | [id ‘.’] ‘this’ - | [id ‘.’] ‘super’ [ClassQualifier] ‘.’ id ; + | [id ‘.’] ‘super’ [ClassQualifier] ‘.’ id -ClassQualifier ::= ‘[’ id ‘]’ ; +ClassQualifier ::= ‘[’ id ‘]’ ``` ### Types -```ebnf +``` Type ::= FunType | HkTypeParamClause ‘=>>’ Type | FunParamClause ‘=>>’ Type | MatchType - | InfixType ; + | InfixType FunType ::= FunTypeArgs (‘=>’ | ‘?=>’) Type - | HKTypeParamClause '=>' Type ; + | HKTypeParamClause '=>' Type FunTypeArgs ::= InfixType | ‘(’ [ FunArgTypes ] ‘)’ - | FunParamClause ; -FunParamClause ::= ‘(’ TypedFunParam {‘,’ TypedFunParam } ‘)’ ; -TypedFunParam ::= id ‘:’ Type ; -MatchType ::= InfixType `match` <<< TypeCaseClauses >>> ; -InfixType ::= RefinedType {id [nl] RefinedType} ; -RefinedType ::= AnnotType {[nl] Refinement} ; -AnnotType ::= SimpleType {Annotation} ; + | FunParamClause +FunParamClause ::= ‘(’ TypedFunParam {‘,’ TypedFunParam } ‘)’ +TypedFunParam ::= id ‘:’ Type +MatchType ::= InfixType `match` <<< TypeCaseClauses >>> +InfixType ::= RefinedType {id [nl] RefinedType} +RefinedType ::= AnnotType {[nl] Refinement} +AnnotType ::= SimpleType {Annotation} SimpleType ::= SimpleLiteral | ‘?’ TypeBounds @@ -185,34 +185,34 @@ SimpleType ::= SimpleLiteral | ‘$’ ‘{’ Block ‘}’ -- unless inside quoted pattern | ‘$’ ‘{’ Pattern ‘}’ -- only inside quoted pattern | SimpleType1 TypeArgs - | SimpleType1 ‘#’ id ; + | SimpleType1 ‘#’ id Singleton ::= SimpleRef | SimpleLiteral - | Singleton ‘.’ id ; + | Singleton ‘.’ id FunArgType ::= Type - | ‘=>’ Type ; -FunArgTypes ::= FunArgType { ‘,’ FunArgType } ; -ParamType ::= [‘=>’] ParamValueType ; -ParamValueType ::= Type [‘*’] ; -TypeArgs ::= ‘[’ Types ‘]’ ; -Refinement ::= ‘{’ [RefineDcl] {semi [RefineDcl]} ‘}’ ; -TypeBounds ::= [‘>:’ Type] [‘<:’ Type] ; -TypeParamBounds ::= TypeBounds {‘:’ Type} ; -Types ::= Type {‘,’ Type} ; + | ‘=>’ Type +FunArgTypes ::= FunArgType { ‘,’ FunArgType } +ParamType ::= [‘=>’] ParamValueType +ParamValueType ::= Type [‘*’] +TypeArgs ::= ‘[’ Types ‘]’ +Refinement ::= ‘{’ [RefineDcl] {semi [RefineDcl]} ‘}’ +TypeBounds ::= [‘>:’ Type] [‘<:’ Type] +TypeParamBounds ::= TypeBounds {‘:’ Type} +Types ::= Type {‘,’ Type} ``` ### Expressions -```ebnf +``` Expr ::= FunParams (‘=>’ | ‘?=>’) Expr | HkTypeParamClause ‘=>’ Expr - | Expr1 ; + | Expr1 BlockResult ::= FunParams (‘=>’ | ‘?=>’) Block | HkTypeParamClause ‘=>’ Block - | Expr1 ; + | Expr1 FunParams ::= Bindings | id - | ‘_’ ; + | ‘_’ Expr1 ::= [‘inline’] ‘if’ ‘(’ Expr ‘)’ {nl} Expr [[semi] ‘else’ Expr] | [‘inline’] ‘if’ Expr ‘then’ Expr [[semi] ‘else’ Expr] | ‘while’ ‘(’ Expr ‘)’ {nl} Expr @@ -226,17 +226,17 @@ Expr1 ::= [‘inline’] ‘if’ ‘(’ Expr ‘)’ {nl} Expr [[ | PrefixOperator SimpleExpr ‘=’ Expr | SimpleExpr ArgumentExprs ‘=’ Expr | PostfixExpr [Ascription] - | ‘inline’ InfixExpr MatchClause ; + | ‘inline’ InfixExpr MatchClause Ascription ::= ‘:’ InfixType - | ‘:’ Annotation {Annotation} ; -Catches ::= ‘catch’ (Expr | ExprCaseClause) ; -PostfixExpr ::= InfixExpr [id] -- only if language.postfixOperators is enabled ; + | ‘:’ Annotation {Annotation} +Catches ::= ‘catch’ (Expr | ExprCaseClause) +PostfixExpr ::= InfixExpr [id] -- only if language.postfixOperators is enabled InfixExpr ::= PrefixExpr | InfixExpr id [nl] InfixExpr - | InfixExpr MatchClause ; -MatchClause ::= ‘match’ <<< CaseClauses >>> ; -PrefixExpr ::= [PrefixOperator] SimpleExpr ; -PrefixOperator ::= ‘-’ | ‘+’ | ‘~’ | ‘!’ ; + | InfixExpr MatchClause +MatchClause ::= ‘match’ <<< CaseClauses >>> +PrefixExpr ::= [PrefixOperator] SimpleExpr +PrefixOperator ::= ‘-’ | ‘+’ | ‘~’ | ‘!’ SimpleExpr ::= SimpleRef | Literal | ‘_’ @@ -251,174 +251,175 @@ SimpleExpr ::= SimpleRef | SimpleExpr ‘.’ id | SimpleExpr ‘.’ MatchClause | SimpleExpr TypeArgs - | SimpleExpr ArgumentExprs ; + | SimpleExpr ArgumentExprs Quoted ::= ‘'’ ‘{’ Block ‘}’ - | ‘'’ ‘[’ Type ‘]’ ; -ExprsInParens ::= ExprInParens {‘,’ ExprInParens} ; + | ‘'’ ‘[’ Type ‘]’ +ExprsInParens ::= ExprInParens {‘,’ ExprInParens} ExprInParens ::= PostfixExpr ‘:’ Type - | Expr ; + | Expr ParArgumentExprs ::= ‘(’ [‘using’] ExprsInParens ‘)’ - | ‘(’ [ExprsInParens ‘,’] PostfixExpr ‘*’ ‘)’ ; + | ‘(’ [ExprsInParens ‘,’] PostfixExpr ‘*’ ‘)’ ArgumentExprs ::= ParArgumentExprs - | BlockExpr ; -BlockExpr ::= <<< (CaseClauses | Block) >>> ; -Block ::= {BlockStat semi} [BlockResult] ; + | BlockExpr +BlockExpr ::= <<< (CaseClauses | Block) >>> +Block ::= {BlockStat semi} [BlockResult] BlockStat ::= Import | {Annotation {nl}} {LocalModifier} Def | Extension | Expr1 - | EndMarker ; + | EndMarker ForExpr ::= ‘for’ ‘(’ Enumerators0 ‘)’ {nl} [‘do‘ | ‘yield’] Expr | ‘for’ ‘{’ Enumerators0 ‘}’ {nl} [‘do‘ | ‘yield’] Expr - | ‘for’ Enumerators0 (‘do‘ | ‘yield’) Expr ; -Enumerators0 ::= {nl} Enumerators [semi] ; -Enumerators ::= Generator {semi Enumerator | Guard} ; + | ‘for’ Enumerators0 (‘do‘ | ‘yield’) Expr +Enumerators0 ::= {nl} Enumerators [semi] +Enumerators ::= Generator {semi Enumerator | Guard} Enumerator ::= Generator | Guard {Guard} - | Pattern1 ‘=’ Expr ; -Generator ::= [‘case’] Pattern1 ‘<-’ Expr ; -Guard ::= ‘if’ PostfixExpr ; - -CaseClauses ::= CaseClause { CaseClause } ; -CaseClause ::= ‘case’ Pattern [Guard] ‘=>’ Block ; -ExprCaseClause ::= ‘case’ Pattern [Guard] ‘=>’ Expr ; -TypeCaseClauses ::= TypeCaseClause { TypeCaseClause } ; -TypeCaseClause ::= ‘case’ InfixType ‘=>’ Type [semi] ; - -Pattern ::= Pattern1 { ‘|’ Pattern1 } ; -Pattern1 ::= Pattern2 [‘:’ RefinedType] ; -Pattern2 ::= [id ‘@’] InfixPattern [‘*’] ; -InfixPattern ::= SimplePattern { id [nl] SimplePattern } ; + | Pattern1 ‘=’ Expr +Generator ::= [‘case’] Pattern1 ‘<-’ Expr +Guard ::= ‘if’ PostfixExpr + +CaseClauses ::= CaseClause { CaseClause } +CaseClause ::= ‘case’ Pattern [Guard] ‘=>’ Block +ExprCaseClause ::= ‘case’ Pattern [Guard] ‘=>’ Expr +TypeCaseClauses ::= TypeCaseClause { TypeCaseClause } +TypeCaseClause ::= ‘case’ InfixType ‘=>’ Type [semi] + +Pattern ::= Pattern1 { ‘|’ Pattern1 } +Pattern1 ::= Pattern2 [‘:’ RefinedType] +Pattern2 ::= [id ‘@’] InfixPattern [‘*’] +InfixPattern ::= SimplePattern { id [nl] SimplePattern } SimplePattern ::= PatVar | Literal | ‘(’ [Patterns] ‘)’ | Quoted | SimplePattern1 [TypeArgs] [ArgumentPatterns] - | ‘given’ RefinedType ; + | ‘given’ RefinedType SimplePattern1 ::= SimpleRef - | SimplePattern1 ‘.’ id ; + | SimplePattern1 ‘.’ id PatVar ::= varid - | ‘_’ ; -Patterns ::= Pattern {‘,’ Pattern} ; + | ‘_’ +Patterns ::= Pattern {‘,’ Pattern} ArgumentPatterns ::= ‘(’ [Patterns] ‘)’ - | ‘(’ [Patterns ‘,’] PatVar ‘*’ ‘)’ ; + | ‘(’ [Patterns ‘,’] PatVar ‘*’ ‘)’ ``` ### Type and Value Parameters -```ebnf -ClsTypeParamClause::= ‘[’ ClsTypeParam {‘,’ ClsTypeParam} ‘]’ ; -ClsTypeParam ::= {Annotation} [‘+’ | ‘-’] id [HkTypeParamClause] TypeParamBounds ; +``` +ClsTypeParamClause::= ‘[’ ClsTypeParam {‘,’ ClsTypeParam} ‘]’ +ClsTypeParam ::= {Annotation} [‘+’ | ‘-’] id [HkTypeParamClause] TypeParamBounds -DefTypeParamClause::= ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’ ; -DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeParamBounds ; +DefTypeParamClause::= ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’ +DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeParamBounds -TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’ ; -TypTypeParam ::= {Annotation} id [HkTypeParamClause] TypeBounds ; +TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’ +TypTypeParam ::= {Annotation} id [HkTypeParamClause] TypeBounds -HkTypeParamClause ::= ‘[’ HkTypeParam {‘,’ HkTypeParam} ‘]’ ; -HkTypeParam ::= {Annotation} [‘+’ | ‘-’] (id [HkTypeParamClause] | ‘_’) TypeBounds ; +HkTypeParamClause ::= ‘[’ HkTypeParam {‘,’ HkTypeParam} ‘]’ +HkTypeParam ::= {Annotation} [‘+’ | ‘-’] (id [HkTypeParamClause] | ‘_’) TypeBounds -ClsParamClauses ::= {ClsParamClause} [[nl] ‘(’ [‘implicit’] ClsParams ‘)’] ; +ClsParamClauses ::= {ClsParamClause} [[nl] ‘(’ [‘implicit’] ClsParams ‘)’] 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 ; + | [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 ``` ### Bindings and Imports -```ebnf -Bindings ::= ‘(’ [Binding {‘,’ Binding}] ‘)’ ; -Binding ::= (id | ‘_’) [‘:’ Type] ; +``` +Bindings ::= ‘(’ [Binding {‘,’ Binding}] ‘)’ +Binding ::= (id | ‘_’) [‘:’ Type] Modifier ::= LocalModifier | AccessModifier | ‘override’ - | ‘opaque’ ; + | ‘opaque’ LocalModifier ::= ‘abstract’ | ‘final’ | ‘sealed’ | ‘open’ | ‘implicit’ | ‘lazy’ - | ‘inline’ ; -AccessModifier ::= (‘private’ | ‘protected’) [AccessQualifier] ; -AccessQualifier ::= ‘[’ id ‘]’ ; + | ‘inline’ +AccessModifier ::= (‘private’ | ‘protected’) [AccessQualifier] +AccessQualifier ::= ‘[’ id ‘]’ -Annotation ::= ‘@’ SimpleType1 {ParArgumentExprs} ; +Annotation ::= ‘@’ SimpleType1 {ParArgumentExprs} -Import ::= ‘import’ ImportExpr {‘,’ ImportExpr} ; -Export ::= ‘export’ ImportExpr {‘,’ ImportExpr} ; +Import ::= ‘import’ ImportExpr {‘,’ ImportExpr} +Export ::= ‘export’ ImportExpr {‘,’ ImportExpr} ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec - | SimpleRef ‘as’ id ; + | SimpleRef ‘as’ id ImportSpec ::= NamedSelector | WildcardSelector - | ‘{’ ImportSelectors) ‘}’ ; -NamedSelector ::= id [‘as’ (id | ‘_’)] ; -WildCardSelector ::= ‘*' | ‘given’ [InfixType] ; + | ‘{’ ImportSelectors) ‘}’ +NamedSelector ::= id [‘as’ (id | ‘_’)] +WildCardSelector ::= ‘*' | ‘given’ [InfixType] ImportSelectors ::= NamedSelector [‘,’ ImportSelectors] - | WildCardSelector {‘,’ WildCardSelector} ; + | WildCardSelector {‘,’ WildCardSelector} -EndMarker ::= ‘end’ EndMarkerTag -- when followed by EOL ; +EndMarker ::= ‘end’ EndMarkerTag -- when followed by EOL EndMarkerTag ::= id | ‘if’ | ‘while’ | ‘for’ | ‘match’ | ‘try’ - | ‘new’ | ‘this’ | ‘given’ | ‘extension’ | ‘val’ ; + | ‘new’ | ‘this’ | ‘given’ | ‘extension’ | ‘val’ ``` ### Declarations and Definitions -```ebnf +``` RefineDcl ::= ‘val’ ValDcl | ‘def’ DefDcl - | ‘type’ {nl} TypeDcl ; + | ‘type’ {nl} TypeDcl Dcl ::= RefineDcl - | ‘var’ VarDcl ; -ValDcl ::= ids ‘:’ Type ; -VarDcl ::= ids ‘:’ Type ; -DefDcl ::= DefSig ‘:’ Type ; -DefSig ::= id [DefTypeParamClause] DefParamClauses ; -TypeDcl ::= id [TypeParamClause] {FunParamClause} TypeBounds [‘=’ Type] ; + | ‘var’ VarDcl +ValDcl ::= ids ‘:’ Type +VarDcl ::= ids ‘:’ Type +DefDcl ::= DefSig ‘:’ Type +DefSig ::= id [DefTypeParamClause] DefParamClauses +TypeDcl ::= id [TypeParamClause] {FunParamClause} TypeBounds [‘=’ Type] Def ::= ‘val’ PatDef | ‘var’ PatDef | ‘def’ DefDef | ‘type’ {nl} TypeDcl - | TmplDef ; + | TmplDef PatDef ::= ids [‘:’ Type] ‘=’ Expr - | Pattern2 [‘:’ Type] ‘=’ Expr ; + | Pattern2 [‘:’ Type] ‘=’ Expr DefDef ::= DefSig [‘:’ Type] ‘=’ Expr - | ‘this’ DefParamClause DefParamClauses ‘=’ ConstrExpr ; + | ‘this’ DefParamClause DefParamClauses ‘=’ ConstrExpr TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef | [‘case’] ‘object’ ObjectDef | ‘enum’ EnumDef - | ‘given’ GivenDef ; -ClassDef ::= id ClassConstr [Template] ; -ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses ; -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 ; -StructuralInstance ::= ConstrApp {‘with’ ConstrApp} [‘with’ TemplateBody] ; + | ‘given’ GivenDef +ClassDef ::= id ClassConstr [Template] +ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses +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 +StructuralInstance ::= ConstrApp {‘with’ ConstrApp} [‘with’ TemplateBody] Extension ::= ‘extension’ [DefTypeParamClause] {UsingParamClause} - ‘(’ DefParam ‘)’ {UsingParamClause} ExtMethods ; -ExtMethods ::= ExtMethod | [nl] <<< ExtMethod {semi ExtMethod} >>> ; -ExtMethod ::= {Annotation [nl]} {Modifier} ‘def’ DefDef ; -Template ::= InheritClauses [TemplateBody] ; -InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}] ; -ConstrApps ::= ConstrApp ({‘,’ ConstrApp} | {‘with’ ConstrApp}) ; -ConstrApp ::= SimpleType1 {Annotation} {ParArgumentExprs} ; + ‘(’ DefParam ‘)’ {UsingParamClause} ExtMethods +ExtMethods ::= ExtMethod | [nl] <<< ExtMethod {semi ExtMethod} >>> +ExtMethod ::= {Annotation [nl]} {Modifier} ‘def’ DefDef + | Export +Template ::= InheritClauses [TemplateBody] +InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}] +ConstrApps ::= ConstrApp ({‘,’ ConstrApp} | {‘with’ ConstrApp}) +ConstrApp ::= SimpleType1 {Annotation} {ParArgumentExprs} ConstrExpr ::= SelfInvocation - | <<< SelfInvocation {semi BlockStat} >>> ; -SelfInvocation ::= ‘this’ ArgumentExprs {ArgumentExprs} ; + | <<< SelfInvocation {semi BlockStat} >>> +SelfInvocation ::= ‘this’ ArgumentExprs {ArgumentExprs} -TemplateBody ::= :<<< [SelfType] TemplateStat {semi TemplateStat} >>> ; +TemplateBody ::= :<<< [SelfType] TemplateStat {semi TemplateStat} >>> TemplateStat ::= Import | Export | {Annotation [nl]} {Modifier} Def @@ -426,16 +427,16 @@ TemplateStat ::= Import | Extension | Expr1 | EndMarker - | ; + | SelfType ::= id [‘:’ InfixType] ‘=>’ - | ‘this’ ‘:’ InfixType ‘=>’ ; + | ‘this’ ‘:’ InfixType ‘=>’ -EnumBody ::= :<<< [SelfType] EnumStat {semi EnumStat} >>> ; +EnumBody ::= :<<< [SelfType] EnumStat {semi EnumStat} >>> EnumStat ::= TemplateStat - | {Annotation [nl]} {Modifier} EnumCase ; -EnumCase ::= ‘case’ (id ClassConstr [‘extends’ ConstrApps]] | ids) ; + | {Annotation [nl]} {Modifier} EnumCase +EnumCase ::= ‘case’ (id ClassConstr [‘extends’ ConstrApps]] | ids) -TopStats ::= TopStat {semi TopStat} ; +TopStats ::= TopStat {semi TopStat} TopStat ::= Import | Export | {Annotation [nl]} {Modifier} Def @@ -443,9 +444,9 @@ TopStat ::= Import | Packaging | PackageObject | EndMarker - | ; -Packaging ::= ‘package’ QualId :<<< TopStats >>> ; -PackageObject ::= ‘package’ ‘object’ ObjectDef ; + | +Packaging ::= ‘package’ QualId :<<< TopStats >>> +PackageObject ::= ‘package’ ‘object’ ObjectDef -CompilationUnit ::= {‘package’ QualId semi} TopStats ; +CompilationUnit ::= {‘package’ QualId semi} TopStats ``` diff --git a/tests/neg/exports.check b/tests/neg/exports.check index 577f9e6b47ce..49d8cdf0654b 100644 --- a/tests/neg/exports.check +++ b/tests/neg/exports.check @@ -58,3 +58,9 @@ | Double definition: | val bar: Bar in class Baz at line 45 and | final def bar: (Baz.this.bar.bar : => (Baz.this.bar.baz.bar : Bar)) in class Baz at line 46 +-- [E083] Type Error: tests/neg/exports.scala:57:11 -------------------------------------------------------------------- +57 | export printer.* // error: not stable + | ^^^^^^^ + | (No.printer : => Printer) is not a valid export prefix, since it is not an immutable path + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/exports.scala b/tests/neg/exports.scala index e0d834e39403..c187582c940d 100644 --- a/tests/neg/exports.scala +++ b/tests/neg/exports.scala @@ -49,3 +49,9 @@ val baz: Baz = new Baz export baz._ } + + object No: + def printer = + println("new Printer") + new Printer + export printer.* // error: not stable diff --git a/tests/pos/reference/exports.scala b/tests/pos/reference/exports.scala index fe924c57e489..8588a2b4cd9d 100644 --- a/tests/pos/reference/exports.scala +++ b/tests/pos/reference/exports.scala @@ -1,3 +1,4 @@ +object exports: class BitMap class InkJet @@ -22,7 +23,22 @@ def status: List[String] = printUnit.status ++ scanUnit.status } - class C22 { type T } - object O22 { val c: C22 = ??? } - export O22.c - def f22: c.T = ??? + class C { type T } + object O { val c: C = ??? } + export O.c + def f: c.T = ??? + + class StringOps(x: String): + def *(n: Int): String = ??? + def ::(c: Char) = c.toString + x + def capitalize: String = ??? + + extension (x: String) + def take(n: Int): String = x.substring(0, n) + def drop(n: Int): String = x.substring(n) + private def moreOps = new StringOps(x) + export moreOps.* + + val s = "abc" + val t1 = (s.take(1) + s.drop(1)).capitalize * 2 + val t2 = 'a' :: s diff --git a/tests/run/export-in-extension.scala b/tests/run/export-in-extension.scala new file mode 100644 index 000000000000..903e13e89269 --- /dev/null +++ b/tests/run/export-in-extension.scala @@ -0,0 +1,23 @@ +object O: + + class C(x: Int): + def bar = x + def baz(y: Int) = x + y + val bam = x * x + def :: (y: Int) = x - y + + extension (x: Int) + private def cm = new C(x) + export cm.* + def succ: Int = x + 1 + def succ2: Int = succ + 1 + def ::: (y: Int) = x - y + +@main def Test = + import O.* + assert(3.succ2 == 5) + assert(3.bar == 3) + assert(3.baz(3) == 6) + assert(3.bam == 9) + assert(3 :: 2 :: 10 == (10 - 2) - 3) // same as for implicit class C + assert(3 ::: 2 ::: 10 == 3 - (2 - 10)) From db1d862ec9c9be34e9663155a2d9a0fecfb7ce12 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 16 Feb 2022 15:07:34 +0100 Subject: [PATCH 2/7] Add tests for error conditions --- .../src/dotty/tools/dotc/typer/Namer.scala | 5 ++- .../src/dotty/tools/dotc/typer/Typer.scala | 11 +++-- tests/neg/export-in-extension.check | 29 ++++++++++++++ tests/neg/export-in-extension.scala | 40 +++++++++++++++++++ tests/run/export-in-extension.scala | 9 +++++ 5 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 tests/neg/export-in-extension.check create mode 100644 tests/neg/export-in-extension.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 9061466fcf14..4cc0d19fd07c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1114,7 +1114,7 @@ class Namer { typer: Typer => else if sym.isAllOf(JavaModule) then Skip else if pathMethod.exists && mbr.isType then - No("cannot be exported as extension method") + No("is a type, so it cannot be exported as extension method") else Yes } @@ -1364,7 +1364,8 @@ class Namer { typer: Typer => process(stats1)(using ctx.importContext(stat, symbolOfTree(stat))) case (stat: ExtMethods) :: stats1 => for case exp: Export <- stat.methods do - processExport(exp, exportPathSym(exp.expr, stat)) + val pathSym = exportPathSym(exp.expr, stat) + if pathSym.exists then processExport(exp, pathSym) process(stats1) case stat :: stats1 => process(stats1) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 26414ffdb461..f34e25dd40ca 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2616,10 +2616,13 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val selectors1 = typedSelectors(imp.selectors) assignType(cpy.Import(imp)(expr1, selectors1), sym) - def typedExport(exp: untpd.Export)(using Context): Export = - val expr1 = exp.expr.removeAttachment(TypedAhead).getOrElse(EmptyTree) - val selectors1 = typedSelectors(exp.selectors) - assignType(cpy.Export(exp)(expr1, selectors1)) + def typedExport(exp: untpd.Export)(using Context): Tree = + exp.expr.removeAttachment(TypedAhead) match + case Some(expr1) => + val selectors1 = typedSelectors(exp.selectors) + assignType(cpy.Export(exp)(expr1, selectors1)) + case _ => + errorTree(exp, em"exports are only allowed from objects and classes") def typedPackageDef(tree: untpd.PackageDef)(using Context): Tree = val pid1 = withMode(Mode.InPackageClauseName)(typedExpr(tree.pid, AnySelectionProto)) diff --git a/tests/neg/export-in-extension.check b/tests/neg/export-in-extension.check new file mode 100644 index 000000000000..43863ebf6ced --- /dev/null +++ b/tests/neg/export-in-extension.check @@ -0,0 +1,29 @@ +-- Error: tests/neg/export-in-extension.scala:14:13 -------------------------------------------------------------------- +14 | export c1.* // error + | ^^ + | export qualifier c1 is not a parameterless companion extension method +-- Error: tests/neg/export-in-extension.scala:19:22 -------------------------------------------------------------------- +19 | export cm.{bar, D} // error + | ^ + | no eligible member D at O.O2.cm + | O.O2.cm.D cannot be exported because it is a type, so it cannot be exported as extension method +-- Error: tests/neg/export-in-extension.scala:20:18 -------------------------------------------------------------------- +20 | export this.cm.baz // error + | ^^^^^^^ + | export qualifier must be a simple reference to a companion extension method +-- Error: tests/neg/export-in-extension.scala:24:13 -------------------------------------------------------------------- +24 | export missing.* // error + | ^^^^^^^ + | export qualifier missing is not a parameterless companion extension method +-- Error: tests/neg/export-in-extension.scala:28:13 -------------------------------------------------------------------- +28 | export cm.* // error + | ^^ + | export qualifier cm is not a parameterless companion extension method +-- Error: tests/neg/export-in-extension.scala:33:13 -------------------------------------------------------------------- +33 | export cm.* // error + | ^^ + | export qualifier cm is not a parameterless companion extension method +-- Error: tests/neg/export-in-extension.scala:38:13 -------------------------------------------------------------------- +38 | export cm.* // error + | ^^^^^^^^^^^ + | exports are only allowed from objects and classes diff --git a/tests/neg/export-in-extension.scala b/tests/neg/export-in-extension.scala new file mode 100644 index 000000000000..8bcab0e5ac8d --- /dev/null +++ b/tests/neg/export-in-extension.scala @@ -0,0 +1,40 @@ +object O: + + class C(x: Int): + def bar = x + def baz(y: Int) = x + y + val bam = x * x + def :: (y: Int) = x - y + class D + + val c1 = C(1) + + object O1: + extension (x: Int) + export c1.* // error + + object O2: + extension (x: Int) + private def cm = new C(x) + export cm.{bar, D} // error + export this.cm.baz // error + + object O3: + extension (x: Int) + export missing.* // error + + object O4: + extension (x: Int) + export cm.* // error + + object O5: + extension (x: Int) + private def cm(y: C) = C + export cm.* // error + + { + extension (x: Int) + private def cm = new C(x) + export cm.* // error + } + diff --git a/tests/run/export-in-extension.scala b/tests/run/export-in-extension.scala index 903e13e89269..26becc280ff3 100644 --- a/tests/run/export-in-extension.scala +++ b/tests/run/export-in-extension.scala @@ -13,6 +13,15 @@ object O: def succ2: Int = succ + 1 def ::: (y: Int) = x - y +object O2: + import O.C + extension (x: Int) + private def cm = new C(x) + export cm.{bar, baz, bam, ::} + def succ: Int = x + 1 + def succ2: Int = succ + 1 + def ::: (y: Int) = x - y + @main def Test = import O.* assert(3.succ2 == 5) From e83d59c114ffd9ec8c64c711d788ea06080b11b7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 19 Feb 2022 12:29:30 +0100 Subject: [PATCH 3/7] Avoid parameter name clashes in export forwarders --- .../src/dotty/tools/dotc/typer/Namer.scala | 27 +++++++++++++++---- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/neg/export-in-extension.check | 2 +- tests/pos/export-in-extension-rename.scala | 7 +++++ 4 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 tests/pos/export-in-extension-rename.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 4cc0d19fd07c..fa055c93da72 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1180,11 +1180,28 @@ class Namer { typer: Typer => then (StableRealizable, ExprType(pathType.select(sym))) else - def addPathMethodParams(pt: Type, info: Type): Type = pt match - case pt: MethodOrPoly => - pt.derivedLambdaType(resType = addPathMethodParams(pt.resType, info)) - case _ => - info + def addPathMethodParams(pathType: Type, info: Type): Type = + def defines(pt: Type, pname: Name): Boolean = pt match + case pt: MethodOrPoly => + pt.paramNames.contains(pname) || defines(pt.resType, pname) + case _ => + false + def avoidNameClashes(info: Type): Type = info match + case info: MethodOrPoly => + info.derivedLambdaType( + paramNames = info.paramNames.mapConserve { + pname => if defines(pathType, pname) then pname.freshened else pname + }, + resType = avoidNameClashes(info.resType)) + case info => + info + def wrap(pt: Type, info: Type): Type = pt match + case pt: MethodOrPoly => + pt.derivedLambdaType(resType = wrap(pt.resType, info)) + case _ => + info + wrap(pathType, avoidNameClashes(info)) + val mbrInfo = if pathMethod.exists then addPathMethodParams(pathMethod.info, mbr.info.widenExpr) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index f34e25dd40ca..659b1342698e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2622,7 +2622,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val selectors1 = typedSelectors(exp.selectors) assignType(cpy.Export(exp)(expr1, selectors1)) case _ => - errorTree(exp, em"exports are only allowed from objects and classes") + errorTree(exp, em"exports are only allowed from objects and classes, they can not belong to local blocks") def typedPackageDef(tree: untpd.PackageDef)(using Context): Tree = val pid1 = withMode(Mode.InPackageClauseName)(typedExpr(tree.pid, AnySelectionProto)) diff --git a/tests/neg/export-in-extension.check b/tests/neg/export-in-extension.check index 43863ebf6ced..3f510291b304 100644 --- a/tests/neg/export-in-extension.check +++ b/tests/neg/export-in-extension.check @@ -26,4 +26,4 @@ -- Error: tests/neg/export-in-extension.scala:38:13 -------------------------------------------------------------------- 38 | export cm.* // error | ^^^^^^^^^^^ - | exports are only allowed from objects and classes + | exports are only allowed from objects and classes, they can not belong to local blocks diff --git a/tests/pos/export-in-extension-rename.scala b/tests/pos/export-in-extension-rename.scala new file mode 100644 index 000000000000..6fb64f392c7a --- /dev/null +++ b/tests/pos/export-in-extension-rename.scala @@ -0,0 +1,7 @@ +class Ops[A](xs: List[A]): + def map[B](x: A => B): List[B] = ??? + +extension [B](x: List[B]) + private def ops = new Ops[B](x) + export ops.map // `x` and `B` should not appear twice as a parameter + From 5749a3c99dd94247cc2de77292567f8458e418f3 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 23 Feb 2022 14:07:33 +0100 Subject: [PATCH 4/7] Add note on export of extensions --- .../reference/other-new-features/export.md | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/docs/_docs/reference/other-new-features/export.md b/docs/_docs/reference/other-new-features/export.md index bd2d42e653a1..6b449efac4c6 100644 --- a/docs/_docs/reference/other-new-features/export.md +++ b/docs/_docs/reference/other-new-features/export.md @@ -162,9 +162,11 @@ extension (x: String) private def moreOps = new StringOps(x) export moreOps.* ``` -In this case the qualifier expression must be an identifier that refers to a unique parameterless extension method in the same extension clause. The export will create -extension methods for all accessible term members -in the result of the qualifier path. For instance, the extension above would be expanded to +In this case the qualifier expression must be an identifier that refers to a unique parameterless extension method in the same extension clause. + +An export will then create extension methods for all accessible term members, +matching the selectors, in the result of the qualifier path. +For instance, the extension above would be expanded to ```scala extension (x: String) def take(n: Int): String = x.substring(0, n) @@ -174,6 +176,38 @@ extension (x: String) def capitalize: String = moreOps.capitalize ``` +### A Note on Exporting Extension Methods +**Note:** extension methods can have surprising results if exported from within an extension (i.e. they +are exported as-if they were an ordinary method). Observe the following example: +```scala +class StringOps: + extension (x: String) def capitalize: String = ... + def zero: String = "" + +extension (s: String) + private def moreOps = new StringOps() + export moreOps.* +``` +this would be expanded to +```scala +extension (s: String) + private def moreOps = new StringOps() + def capitalize(x: String): String = moreOps.capitalize(x) + ... +``` +observe the extra String parameter on `capitalize`. +It is instead recommended to export extension methods from +outside of an extension, and to use a renaming selector to avoid them, e.g.: +```scala +private val myStringOps = new StringOps() + +extension (s: String) + private def moreOps = myStringOps + export moreOps.{capitalize as _, *} + +export myStringOps.capitalize +``` + ## Syntax changes: ``` From 733b0114c4d4e8fd3f0d5e4c6925ca2537f821a8 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 23 Feb 2022 14:30:06 +0100 Subject: [PATCH 5/7] add forwardCompat test for export in extension --- .../Lib_1_r3.0.scala | 13 +++++++++++++ .../Test_2_c3.0.2.scala | 7 +++++++ 2 files changed, 20 insertions(+) create mode 100644 tests/pos/forwardCompat-exportInExtension/Lib_1_r3.0.scala create mode 100644 tests/pos/forwardCompat-exportInExtension/Test_2_c3.0.2.scala diff --git a/tests/pos/forwardCompat-exportInExtension/Lib_1_r3.0.scala b/tests/pos/forwardCompat-exportInExtension/Lib_1_r3.0.scala new file mode 100644 index 000000000000..099782b74c8a --- /dev/null +++ b/tests/pos/forwardCompat-exportInExtension/Lib_1_r3.0.scala @@ -0,0 +1,13 @@ +case class ShortString(value: String) + +class StringOps(x: ShortString): + def *(n: Int): ShortString = ??? + def capitalize: ShortString = ??? + +object stringsyntax: + + extension (x: ShortString) + def take(n: Int): ShortString = ??? + def drop(n: Int): ShortString = ??? + private def moreOps = StringOps(x) + export moreOps.* diff --git a/tests/pos/forwardCompat-exportInExtension/Test_2_c3.0.2.scala b/tests/pos/forwardCompat-exportInExtension/Test_2_c3.0.2.scala new file mode 100644 index 000000000000..0abe6283609f --- /dev/null +++ b/tests/pos/forwardCompat-exportInExtension/Test_2_c3.0.2.scala @@ -0,0 +1,7 @@ +import stringsyntax.* + +def test = + ShortString("Hello").take(2) + ShortString("Hello").drop(3) + ShortString("Hello") * 5 + ShortString("Hello").capitalize From 93143888cb37058e8cc317f7fc81d8a70e1376c6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 24 Feb 2022 11:41:10 +0100 Subject: [PATCH 6/7] Disallow exports of extension methods in extension methods Revert "Add note on export of extensions" This reverts commit 77447c892b08e733dbdbdbb731800e7015c551de. --- .../src/dotty/tools/dotc/typer/Namer.scala | 2 + .../reference/other-new-features/export.md | 40 ++----------------- tests/neg/export-extension-in-extension.scala | 16 ++++++++ 3 files changed, 21 insertions(+), 37 deletions(-) create mode 100644 tests/neg/export-extension-in-extension.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index fa055c93da72..7e51a42428f9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1115,6 +1115,8 @@ class Namer { typer: Typer => Skip else if pathMethod.exists && mbr.isType then No("is a type, so it cannot be exported as extension method") + else if pathMethod.exists && sym.is(ExtensionMethod) then + No("is already an extension method, cannot be exported into another one") else Yes } diff --git a/docs/_docs/reference/other-new-features/export.md b/docs/_docs/reference/other-new-features/export.md index 6b449efac4c6..bd2d42e653a1 100644 --- a/docs/_docs/reference/other-new-features/export.md +++ b/docs/_docs/reference/other-new-features/export.md @@ -162,11 +162,9 @@ extension (x: String) private def moreOps = new StringOps(x) export moreOps.* ``` -In this case the qualifier expression must be an identifier that refers to a unique parameterless extension method in the same extension clause. - -An export will then create extension methods for all accessible term members, -matching the selectors, in the result of the qualifier path. -For instance, the extension above would be expanded to +In this case the qualifier expression must be an identifier that refers to a unique parameterless extension method in the same extension clause. The export will create +extension methods for all accessible term members +in the result of the qualifier path. For instance, the extension above would be expanded to ```scala extension (x: String) def take(n: Int): String = x.substring(0, n) @@ -176,38 +174,6 @@ extension (x: String) def capitalize: String = moreOps.capitalize ``` -### A Note on Exporting Extension Methods -**Note:** extension methods can have surprising results if exported from within an extension (i.e. they -are exported as-if they were an ordinary method). Observe the following example: -```scala -class StringOps: - extension (x: String) def capitalize: String = ... - def zero: String = "" - -extension (s: String) - private def moreOps = new StringOps() - export moreOps.* -``` -this would be expanded to -```scala -extension (s: String) - private def moreOps = new StringOps() - def capitalize(x: String): String = moreOps.capitalize(x) - ... -``` -observe the extra String parameter on `capitalize`. -It is instead recommended to export extension methods from -outside of an extension, and to use a renaming selector to avoid them, e.g.: -```scala -private val myStringOps = new StringOps() - -extension (s: String) - private def moreOps = myStringOps - export moreOps.{capitalize as _, *} - -export myStringOps.capitalize -``` - ## Syntax changes: ``` diff --git a/tests/neg/export-extension-in-extension.scala b/tests/neg/export-extension-in-extension.scala new file mode 100644 index 000000000000..83c281687d3f --- /dev/null +++ b/tests/neg/export-extension-in-extension.scala @@ -0,0 +1,16 @@ +class StringOps: + extension (x: String) + def capitalize: String = ??? + def foo: String = ??? + + def foo: String = ??? + + +extension (s: String) + private def moreOps = new StringOps() + export moreOps.capitalize // error: no eligible member capitalize at moreOps + +extension (s: String) + private def moreOps2= new StringOps() + export moreOps2.foo // OK + From 3c556740fb5593dc344b4ce3f77b89437c0a29de Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 18 Apr 2022 12:32:08 +0200 Subject: [PATCH 7/7] Tweak duplicate export detection --- compiler/src/dotty/tools/dotc/typer/Namer.scala | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 7e51a42428f9..79e015e0015c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1099,10 +1099,14 @@ class Namer { typer: Typer => val sym = mbr.symbol if !sym.isAccessibleFrom(pathType) then No("is not accessible") - else if sym.isConstructor || sym.is(ModuleClass) || sym.is(Bridge) || sym.is(ConstructorProxy) then + else if sym.isConstructor || sym.is(ModuleClass) || sym.is(Bridge) || sym.is(ConstructorProxy) || sym.isAllOf(JavaModule) then Skip else if cls.derivesFrom(sym.owner) && (sym.owner == cls || !sym.is(Deferred)) then No(i"is already a member of $cls") + else if pathMethod.exists && mbr.isType then + No("is a type, so it cannot be exported as extension method") + else if pathMethod.exists && sym.is(ExtensionMethod) then + No("is already an extension method, cannot be exported into another one") else if targets.contains(alias) then No(i"clashes with another export in the same export clause") else if sym.is(Override) then @@ -1111,12 +1115,6 @@ class Namer { typer: Typer => ) match case Some(other) => No(i"overrides ${other.showLocated}, which is already a member of $cls") case None => Yes - else if sym.isAllOf(JavaModule) then - Skip - else if pathMethod.exists && mbr.isType then - No("is a type, so it cannot be exported as extension method") - else if pathMethod.exists && sym.is(ExtensionMethod) then - No("is already an extension method, cannot be exported into another one") else Yes } @@ -1245,12 +1243,13 @@ class Namer { typer: Typer => val size = buf.size val mbrs = List(name, name.toTypeName).flatMap(pathType.member(_).alternatives) mbrs.foreach(addForwarder(alias, _, span)) - targets += alias if buf.size == size then val reason = mbrs.map(canForward(_, alias)).collect { case CanForward.No(whyNot) => i"\n$path.$name cannot be exported because it $whyNot" }.headOption.getOrElse("") report.error(i"""no eligible member $name at $path$reason""", ctx.source.atSpan(span)) + else + targets += alias def addWildcardForwardersNamed(name: TermName, span: Span): Unit = List(name, name.toTypeName)