diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 3ffef90057ef..aef8ff23e270 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3111,7 +3111,7 @@ object Parsers { */ def importClause(leading: Token, mkTree: ImportConstr): List[Tree] = { val offset = accept(leading) - commaSeparated(importExpr(mkTree)) match { + commaSeparated(importExpr(mkTree, acceptArgs = leading == EXPORT)) match { case t :: rest => // The first import should start at the start offset of the keyword. val firstPos = @@ -3142,6 +3142,8 @@ object Parsers { imp /** ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec + * | SimpleRef ‘as’ id + * ExportExpr ::= SimpleRef {‘.’ id | ParArgumentExprs} ‘.’ ImportSpec * | SimpleRef ‘as’ id * ImportSpec ::= NamedSelector * | WildcardSelector @@ -3151,7 +3153,7 @@ object Parsers { * NamedSelector ::= id [‘as’ (id | ‘_’)] * WildCardSelector ::= ‘*' | ‘given’ [InfixType] */ - def importExpr(mkTree: ImportConstr): () => Tree = + def importExpr(mkTree: ImportConstr, acceptArgs: Boolean): () => Tree = /** ‘*' | ‘_' */ def wildcardSelector() = @@ -3216,6 +3218,8 @@ object Parsers { mkTree(qual1, namedSelector(from) :: Nil) case qual: Ident => mkTree(EmptyTree, namedSelector(qual) :: Nil) + else if acceptArgs && in.token == LPAREN then + importSelection(atSpan(startOffset(qual)) { mkApply(qual, parArgumentExprs()) }) else accept(DOT) in.token match diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index be38221ef167..cd65d9b32ac4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -868,9 +868,15 @@ trait Checking { then report.error(em"no aliases can be used to refer to a language import", path.srcPos) + /** Check that `path` is a legal prefix for an export clause that exports a type */ + def checkLegalExportPathForType(path: Tree, mbr: Symbol)(using Context): Unit = + checkLegalImportOrExportPath(path, + if !path.tpe.isStable // compute non-constant kind string only when we are sure that an error is issued + then i"export prefix for $mbr" + else "export prefix") + /** Check that `path` is a legal prefix for an export clause */ def checkLegalExportPath(path: Tree, selectors: List[untpd.ImportSelector])(using Context): Unit = - checkLegalImportOrExportPath(path, "export prefix") if selectors.exists(_.isWildcard) && path.tpe.classSymbol.is(PackageClass) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 4bca5c50cf77..4afd0dd8ca8a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1064,14 +1064,14 @@ 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] = + private def exportForwarders(exp: Export, localDummy: 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) + val path = typedAheadExpr(expr, AnySelectionProto)(using ctx.withOwner(localDummy)) checkLegalExportPath(path, selectors) lazy val wildcardBound = importBound(selectors, isGiven = false) lazy val givenBound = importBound(selectors, isGiven = true) @@ -1150,7 +1150,11 @@ 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 + if sym.isStableMember + && sym.isPublic + && path.tpe.isStable + && !refersToPrivate(path.tpe) + then (StableRealizable, ExprType(path.tpe.select(sym))) else (EmptyFlags, mbr.info.ensureMethodic) @@ -1165,10 +1169,11 @@ class Namer { typer: Typer => forwarder.addAnnotations(sym.annotations.filterConserve(_.symbol != defn.BodyAnnot)) if forwarder.isType then + checkLegalExportPathForType(path, sym) buf += tpd.TypeDef(forwarder.asType).withSpan(span) else import tpd._ - val ref = path.select(sym.asTerm) + val ref = path.changeOwner(localDummy, forwarder).select(sym.asTerm) val ddef = tpd.DefDef(forwarder.asTerm, prefss => ref.appliedToArgss(adaptForwarderParams(Nil, sym.info, prefss))) if forwarder.isInlineMethod then @@ -1234,11 +1239,11 @@ class Namer { typer: Typer => end exportForwarders /** Add forwarders as required by the export statements in this class */ - private def processExports(using Context): Unit = + private def processExports(localDummy: Symbol)(using Context): Unit = def process(stats: List[Tree])(using Context): Unit = stats match case (stat: Export) :: stats1 => - for forwarder <- exportForwarders(stat) do + for forwarder <- exportForwarders(stat, localDummy) do forwarder.symbol.entered process(stats1) case (stat: Import) :: stats1 => @@ -1435,7 +1440,8 @@ class Namer { typer: Typer => if cls.is(Trait) then cls.is(NoInits) else cls.isNoInitsRealClass if ctorStable then cls.primaryConstructor.setFlag(StableRealizable) - processExports(using localCtx) + val localDummy = recordSym(newLocalDummy(cls, impl.span), impl) + processExports(localDummy)(using localCtx) defn.patchStdLibClass(cls) addConstructorProxies(cls) } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 09acde2ac845..322ff05114a5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1229,7 +1229,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if pos < mtpe.paramInfos.length then mtpe.paramInfos(pos) // This works only if vararg annotations match up. - // See neg/i14367.scala for an example where the inferred type is mispredicted. + // See neg/i14367.scala for an example where the inferred type is mispredicted. // Nevertheless, the alternative would be to give up completely, so this is // defensible. else NoType @@ -2530,7 +2530,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer parent def localDummy(cls: ClassSymbol, impl: untpd.Template)(using Context): Symbol = - newLocalDummy(cls, impl.span) + impl.removeAttachment(SymOfTree).get inline private def typedSelectors(selectors: List[untpd.ImportSelector])(using Context): List[untpd.ImportSelector] = selectors.mapConserve { sel => @@ -2565,8 +2565,8 @@ 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 = typedExpr(exp.expr, AnySelectionProto) + def typedExport(exp: untpd.Export, exprOwner: Symbol)(using Context): Export = + val expr1 = typedExpr(exp.expr, AnySelectionProto)(using ctx.withOwner(exprOwner)) // already called `checkLegalExportPath` in Namer val selectors1 = typedSelectors(exp.selectors) assignType(cpy.Export(exp)(expr1, selectors1)) @@ -2822,7 +2822,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case tree: untpd.Function => typedFunction(tree, pt) case tree: untpd.Closure => typedClosure(tree, pt) case tree: untpd.Import => typedImport(tree, retrieveSym(tree)) - case tree: untpd.Export => typedExport(tree) + case tree: untpd.Export => typedExport(tree, ctx.owner) case tree: untpd.Match => typedMatch(tree, pt) case tree: untpd.Return => typedReturn(tree) case tree: untpd.WhileDo => typedWhileDo(tree) @@ -2982,7 +2982,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case Thicket(stats) :: rest => traverse(stats ::: rest) case (stat: untpd.Export) :: rest => - buf += typed(stat) + buf += typedExport(stat, exprOwner) buf ++= stat.attachmentOrElse(ExportForwarders, Nil) // no attachment can happen in case of cyclic references traverse(rest) diff --git a/docs/_docs/internals/syntax.md b/docs/_docs/internals/syntax.md index c484c42864e5..1827910fd9cc 100644 --- a/docs/_docs/internals/syntax.md +++ b/docs/_docs/internals/syntax.md @@ -259,7 +259,7 @@ SimpleExpr ::= SimpleRef | SimpleExpr ‘_’ PostfixOp(expr, _) (to be dropped) | XmlExpr -- to be dropped IndentedExpr ::= indent CaseClauses | Block outdent -Quoted ::= ‘'’ ‘{’ Block ‘}’ +Quoted ::= ‘'’ ‘{’ Block ‘}’ | ‘'’ ‘[’ Type ‘]’ ExprsInParens ::= ExprInParens {‘,’ ExprInParens} ExprInParens ::= PostfixExpr ‘:’ Type -- normal Expr allows only RefinedType here @@ -366,9 +366,11 @@ AccessQualifier ::= ‘[’ id ‘]’ Annotation ::= ‘@’ SimpleType1 {ParArgumentExprs} Apply(tpe, args) Import ::= ‘import’ ImportExpr {‘,’ ImportExpr} -Export ::= ‘export’ ImportExpr {‘,’ ImportExpr} ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec Import(expr, sels) | SimpleRef ‘as’ id Import(EmptyTree, ImportSelector(ref, id)) +Export ::= ‘export’ ExportExpr {‘,’ ExportExpr} +ExportExpr ::= SimpleRef {‘.’ id | ParArgumentExprs} ‘.’ ImportSpec Export(expr, sels) + | SimpleRef ‘as’ id Export(EmptyTree, ImportSelector(ref, id)) ImportSpec ::= NamedSelector | WildcardSelector | ‘{’ ImportSelectors) ‘}’ diff --git a/docs/_docs/reference/other-new-features/export.md b/docs/_docs/reference/other-new-features/export.md index ce1d536a1432..9c76cc12369f 100644 --- a/docs/_docs/reference/other-new-features/export.md +++ b/docs/_docs/reference/other-new-features/export.md @@ -44,22 +44,22 @@ val copier = new Copier copier.print(copier.scan()) ``` -An `export` clause has the same format as an import clause. Its general form is: +An `export` clause has a similar format as an import clause. Its general form is: ```scala -export path . { sel_1, ..., sel_n } +export qual . { sel_1, ..., sel_n } ``` -It consists of a qualifier expression `path`, which must be a stable identifier, followed by +It consists of a qualifier expression `qual` followed by one or more selectors `sel_i` that identify what gets an alias. Selectors can be of one of the following forms: - - A _simple selector_ `x` creates aliases for all eligible members of `path` that are named `x`. - - A _renaming selector_ `x => y` creates aliases for all eligible members of `path` that are named `x`, but the alias is named `y` instead of `x`. + - A _simple selector_ `x` creates aliases for all eligible members of `qual` that are named `x`. + - A _renaming selector_ `x => y` creates aliases for all eligible members of `qual` that are named `x`, but the alias is named `y` instead of `x`. - An _omitting selector_ `x => _` prevents `x` from being aliased by a subsequent wildcard selector. - A _given selector_ `given x` has an optional type bound `x`. It creates aliases for all eligible given instances that conform to either `x`, or `Any` if `x` is omitted, except for members that are named by a previous simple, renaming, or omitting selector. - - A _wildcard selector_ `*` creates aliases for all eligible members of `path` except for given instances, + - A _wildcard selector_ `*` creates aliases for all eligible members of `qual` except for given instances, synthetic members generated by the compiler and those members that are named by a previous simple, renaming, or omitting selector. \ Notes: @@ -77,7 +77,30 @@ A member is _eligible_ if all of the following holds: 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) +``` +The qualifier expression `qual` can contain selections as well as applications +to arguments. However, if a type member is exported, `qual` must be a stable path. + +Example: + +```scala +class C(x: Int) { type T; def m = x } +export C(2).m // OK, generates: def m = C(2).m +export C(3).T // error: need a path to export T +export C(4).* // also error since T is exported via * +``` + + +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. @@ -85,8 +108,7 @@ Export aliases are always `final`. Aliases of given instances are again defined not marked `override`. - However, export aliases can implement deferred members of base classes. -Export aliases for public value definitions that are accessed without -referring to private values in the qualifier path +If the qualifier is a stable path, export aliases for public value definitions that are accessed without referring to private values in that path are marked by the compiler as "stable" and their result types are the singleton types of the aliased definitions. This means that they can be used as parts of stable identifier paths, even though they are technically methods. For instance, the following is OK: ```scala class C { type T } @@ -99,7 +121,7 @@ def f: c.T = ... **Restrictions:** 1. Export clauses can appear in classes or they can appear at the top-level. An export clause cannot appear as a statement in a block. - 1. If an export clause contains a wildcard or given selector, it is forbidden for its qualifier path to refer to a package. This is because it is not yet known how to safely track wildcard dependencies to a package for the purposes of incremental compilation. + 1. If an export clause contains a wildcard or given selector, it is forbidden for its qualifier to refer to a package. This is because it is not yet known how to safely track wildcard dependencies to a package for the purposes of incremental compilation. 1. Simple renaming exports like ```scala @@ -130,8 +152,9 @@ TemplateStat ::= ... | Export TopStat ::= ... | Export -Export ::= ‘export’ ImportExpr {‘,’ ImportExpr} -ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec +Export ::= ‘export’ ExportExpr {‘,’ ExportExpr} +ExportExpr ::= SimpleRef {‘.’ id | ParArgumentExprs} ‘.’ ImportSpec + | SimpleRef ‘as’ id ImportSpec ::= NamedSelector | WildcardSelector | ‘{’ ImportSelectors) ‘}’ @@ -173,9 +196,8 @@ Export clauses are processed when the type information of the enclosing object o With export clauses, the following steps are added: - 6. Compute the types of all paths in export clauses. - 7. Enter export aliases for the eligible members of all paths in export clauses. + 6. Compute the types of all qualifiers in export clauses. + 7. Enter export aliases for the eligible members of all qualifiers in export clauses. It is important that steps 6 and 7 are done in sequence: We first compute the types of _all_ -paths in export clauses and only after this is done we enter any export aliases as class members. This means that a path of an export clause cannot refer to an alias made available -by another export clause of the same class. +qualifiers in export clauses and only after this is done we enter any export aliases as class members. This means that a qualifier of an export clause cannot refer to an alias made available by another export clause of the same class. diff --git a/docs/_docs/reference/syntax.md b/docs/_docs/reference/syntax.md index ff219a46081c..8a005e8a637f 100644 --- a/docs/_docs/reference/syntax.md +++ b/docs/_docs/reference/syntax.md @@ -355,9 +355,11 @@ AccessQualifier ::= ‘[’ id ‘]’ Annotation ::= ‘@’ SimpleType1 {ParArgumentExprs} Import ::= ‘import’ ImportExpr {‘,’ ImportExpr} -Export ::= ‘export’ ImportExpr {‘,’ ImportExpr} ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec | SimpleRef ‘as’ id +Export ::= ‘export’ ExportExpr {‘,’ ExportExpr} +ExportExpr ::= SimpleRef {‘.’ id | ParArgumentExprs} ‘.’ ImportSpec + | SimpleRef ‘as’ id ImportSpec ::= NamedSelector | WildcardSelector | ‘{’ ImportSelectors) ‘}’ diff --git a/tests/neg/exports.check b/tests/neg/exports.check index 577f9e6b47ce..7e220fd67ab4 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("#1").* // error + | ^^^^^^^^^^^^^ + | Printer is not a valid export prefix for type PrinterType, 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..0870164f4ff7 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(id: String) = + println(s"new Printer $id") + new Printer + export printer("#1").* // error diff --git a/tests/pos/export-macros/A_1.scala b/tests/pos/export-macros/A_1.scala new file mode 100644 index 000000000000..db844548351d --- /dev/null +++ b/tests/pos/export-macros/A_1.scala @@ -0,0 +1,7 @@ +import quoted.* +class Container1(arg: Int): + object Internal1: + def exec: Unit = println("this is Internal1") +transparent inline def myC: Any = ${ macroCrap } +def macroCrap(using Quotes): Expr[Any] = + '{ Container1(1) } \ No newline at end of file diff --git a/tests/pos/export-macros/B_2.scala b/tests/pos/export-macros/B_2.scala new file mode 100644 index 000000000000..65889da89295 --- /dev/null +++ b/tests/pos/export-macros/B_2.scala @@ -0,0 +1 @@ +export myC.* \ No newline at end of file diff --git a/tests/pos/export-transparent.scala b/tests/pos/export-transparent.scala new file mode 100644 index 000000000000..f8da3428d4e9 --- /dev/null +++ b/tests/pos/export-transparent.scala @@ -0,0 +1,7 @@ +class Container(args: Any*): + object Internal + +object Test: + transparent inline def myC(args: Any*): Container = Container(args) + + export myC(1).* diff --git a/tests/pos/reference/exports.scala b/tests/pos/reference/exports.scala index fe924c57e489..09e57ecddb7c 100644 --- a/tests/pos/reference/exports.scala +++ b/tests/pos/reference/exports.scala @@ -1,28 +1,33 @@ - class BitMap - class InkJet +class BitMap +class InkJet - class Printer { - type PrinterType - def print(bits: BitMap): Unit = ??? - def status: List[String] = ??? - } +class Printer { + type PrinterType + def print(bits: BitMap): Unit = ??? + def status: List[String] = ??? +} - class Scanner { - def scan(): BitMap = ??? - def status: List[String] = ??? - } +class Scanner { + def scan(): BitMap = ??? + def status: List[String] = ??? +} - class Copier { - private val printUnit = new Printer { type PrinterType = InkJet } - private val scanUnit = new Scanner +class Copier { + private val printUnit = new Printer { type PrinterType = InkJet } + private val scanUnit = new Scanner - export scanUnit.scan - export printUnit.{status => _, _} + export scanUnit.scan + export printUnit.{status => _, _} - def status: List[String] = printUnit.status ++ scanUnit.status - } + 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 C22 { type T } +object O22 { val c: C22 = ??? } +export O22.c +def f22: c.T = ??? + +object O: + class C23(val x: Int) + def m23(c: C23): Int = c.x + 1 +export O.* \ No newline at end of file diff --git a/tests/run/exports.check b/tests/run/exports.check index 8315f00b6913..31a82a48ca8a 100644 --- a/tests/run/exports.check +++ b/tests/run/exports.check @@ -5,3 +5,8 @@ config printing scanning scanning +new Printer #1 +printing +new Printer #1 +printing +scanning diff --git a/tests/run/exports.scala b/tests/run/exports.scala index 2c03c52c25b4..70817b1f5869 100644 --- a/tests/run/exports.scala +++ b/tests/run/exports.scala @@ -18,8 +18,8 @@ object Test extends App { object Copier { val printer = new Printer - export printer.{given, _} - export Scanner.{scan => scanIt, _} + export printer.{given, *} + export Scanner.{scan => scanIt, *} val config2 = summon[Config] } @@ -41,6 +41,17 @@ object Test extends App { test() val _: Int = B.x + + object FunnyCopier: + def printer(id: String) = + println(s"new Printer $id") + new Printer + export printer("#1").* + export Scanner.* + + FunnyCopier.print() + FunnyCopier.print() + FunnyCopier.scan() } final class Foo {