From 64a298a37c766830ef7831be1a6e1c14a0cbccb6 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Thu, 5 Nov 2020 13:52:42 +0100 Subject: [PATCH 1/4] Keep export clause trees until FirstTransform Add Export trees to TASTy format and QuoteContext reflection API Use Export trees to record wildcard export as dependencies by inheritance in incremental compilation. Motivation for these changes: - enable semantic tools such as doc tools to inspect where exports occur - when a class A exports members of (b: B) with a wildcard, there was no dependency recorded to trigger a recompile of A if B adds new members. Keeping the export trees in the dependencies phase lets us inspect for the wildcard case which has a different dependency relationship than named exports. --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 27 +++- compiler/src/dotty/tools/dotc/ast/tpd.scala | 3 + compiler/src/dotty/tools/dotc/ast/untpd.scala | 10 +- .../src/dotty/tools/dotc/core/Types.scala | 5 + .../tools/dotc/core/tasty/TreePickler.scala | 6 + .../tools/dotc/core/tasty/TreeUnpickler.scala | 12 +- .../dotty/tools/dotc/parsing/Parsers.scala | 4 +- .../tools/dotc/sbt/ExtractDependencies.scala | 25 +++- .../tools/dotc/transform/FirstTransform.scala | 2 +- .../dotc/transform/TreeMapWithStages.scala | 4 +- .../dotty/tools/dotc/typer/TypeAssigner.scala | 3 + .../src/dotty/tools/dotc/typer/Typer.scala | 19 ++- .../quoted/runtime/impl/QuotesImpl.scala | 49 +++++--- .../runtime/impl/printers/Extractors.scala | 8 +- .../runtime/impl/printers/SourceCode.scala | 22 ++-- library/src/scala/quoted/ExprMap.scala | 2 +- library/src/scala/quoted/Quotes.scala | 118 +++++++++++------- .../export-clauses/A.scala | 18 +++ .../export-clauses/B.scala | 3 + .../export-clauses/C.scala | 3 + .../export-clauses/D.scala | 3 + .../export-clauses/build.sbt | 25 ++++ .../export-clauses/changes/B1.scala | 4 + .../export-clauses/changes/C1.scala | 4 + .../export-clauses/changes/D1.scala | 4 + .../export-clauses/project/CompileState.scala | 4 + .../project/DottyInjectedPlugin.scala | 12 ++ .../export-clauses/project/plugins.sbt | 1 + .../source-dependencies/export-clauses/test | 29 +++++ tasty/src/dotty/tools/tasty/TastyFormat.scala | 3 + tests/run-macros/exports.check | 16 +++ tests/run-macros/exports/Logger_1.scala | 3 + tests/run-macros/exports/Macro_2.scala | 58 +++++++++ tests/run-macros/exports/Test_3.scala | 31 +++++ .../expect/exports-example-Codec.expect.scala | 15 +++ .../expect/exports-example-Codec.scala | 15 +++ .../expect/exports-package.expect.scala | 3 + tests/semanticdb/expect/exports-package.scala | 3 + tests/semanticdb/metac.expect | 100 +++++++++++++++ 39 files changed, 580 insertions(+), 96 deletions(-) create mode 100644 sbt-dotty/sbt-test/source-dependencies/export-clauses/A.scala create mode 100644 sbt-dotty/sbt-test/source-dependencies/export-clauses/B.scala create mode 100644 sbt-dotty/sbt-test/source-dependencies/export-clauses/C.scala create mode 100644 sbt-dotty/sbt-test/source-dependencies/export-clauses/D.scala create mode 100644 sbt-dotty/sbt-test/source-dependencies/export-clauses/build.sbt create mode 100644 sbt-dotty/sbt-test/source-dependencies/export-clauses/changes/B1.scala create mode 100644 sbt-dotty/sbt-test/source-dependencies/export-clauses/changes/C1.scala create mode 100644 sbt-dotty/sbt-test/source-dependencies/export-clauses/changes/D1.scala create mode 100644 sbt-dotty/sbt-test/source-dependencies/export-clauses/project/CompileState.scala create mode 100644 sbt-dotty/sbt-test/source-dependencies/export-clauses/project/DottyInjectedPlugin.scala create mode 100644 sbt-dotty/sbt-test/source-dependencies/export-clauses/project/plugins.sbt create mode 100644 sbt-dotty/sbt-test/source-dependencies/export-clauses/test create mode 100644 tests/run-macros/exports.check create mode 100644 tests/run-macros/exports/Logger_1.scala create mode 100644 tests/run-macros/exports/Macro_2.scala create mode 100644 tests/run-macros/exports/Test_3.scala create mode 100644 tests/semanticdb/expect/exports-example-Codec.expect.scala create mode 100644 tests/semanticdb/expect/exports-example-Codec.scala create mode 100644 tests/semanticdb/expect/exports-package.expect.scala create mode 100644 tests/semanticdb/expect/exports-package.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 87d3c11072bb..b6007f4e9d56 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -794,15 +794,31 @@ object Trees { } + abstract class ImportOrExport[-T >: Untyped](implicit @constructorOnly src: SourceFile) + extends DenotingTree[T] { + type ThisTree[-T >: Untyped] <: ImportOrExport[T] + val expr: Tree[T] + val selectors: List[untpd.ImportSelector] + } + /** import expr.selectors * where a selector is either an untyped `Ident`, `name` or * an untyped thicket consisting of `name` and `rename`. */ case class Import[-T >: Untyped] private[ast] (expr: Tree[T], selectors: List[untpd.ImportSelector])(implicit @constructorOnly src: SourceFile) - extends DenotingTree[T] { + extends ImportOrExport[T] { type ThisTree[-T >: Untyped] = Import[T] } + /** export expr.selectors + * where a selector is either an untyped `Ident`, `name` or + * an untyped thicket consisting of `name` and `rename`. + */ + case class Export[-T >: Untyped] private[ast] (expr: Tree[T], selectors: List[untpd.ImportSelector])(implicit @constructorOnly src: SourceFile) + extends ImportOrExport[T] { + type ThisTree[-T >: Untyped] = Export[T] + } + /** package pid { stats } */ case class PackageDef[-T >: Untyped] private[ast] (pid: RefTree[T], stats: List[Tree[T]])(implicit @constructorOnly src: SourceFile) extends ProxyTree[T] { @@ -990,6 +1006,7 @@ object Trees { type TypeDef = Trees.TypeDef[T] type Template = Trees.Template[T] type Import = Trees.Import[T] + type Export = Trees.Export[T] type PackageDef = Trees.PackageDef[T] type Annotated = Trees.Annotated[T] type Thicket = Trees.Thicket[T] @@ -1200,6 +1217,10 @@ object Trees { case tree: Import if (expr eq tree.expr) && (selectors eq tree.selectors) => tree case _ => finalize(tree, untpd.Import(expr, selectors)(sourceFile(tree))) } + def Export(tree: Tree)(expr: Tree, selectors: List[untpd.ImportSelector])(using Context): Export = tree match { + case tree: Export if (expr eq tree.expr) && (selectors eq tree.selectors) => tree + case _ => finalize(tree, untpd.Export(expr, selectors)(sourceFile(tree))) + } def PackageDef(tree: Tree)(pid: RefTree, stats: List[Tree])(using Context): PackageDef = tree match { case tree: PackageDef if (pid eq tree.pid) && (stats eq tree.stats) => tree case _ => finalize(tree, untpd.PackageDef(pid, stats)(sourceFile(tree))) @@ -1350,6 +1371,8 @@ object Trees { cpy.Template(tree)(transformSub(constr), transform(tree.parents), Nil, transformSub(self), transformStats(tree.body)) case Import(expr, selectors) => cpy.Import(tree)(transform(expr), selectors) + case Export(expr, selectors) => + cpy.Export(tree)(transform(expr), selectors) case PackageDef(pid, stats) => cpy.PackageDef(tree)(transformSub(pid), transformStats(stats)(using localCtx)) case Annotated(arg, annot) => @@ -1484,6 +1507,8 @@ object Trees { this(this(this(this(x, constr), parents), self), tree.body) case Import(expr, _) => this(x, expr) + case Export(expr, _) => + this(x, expr) case PackageDef(pid, stats) => this(this(x, pid), stats)(using localCtx) case Annotated(arg, annot) => diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index dc8ad95dd0db..2ec02fceb692 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -361,6 +361,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def Import(expr: Tree, selectors: List[untpd.ImportSelector])(using Context): Import = ta.assignType(untpd.Import(expr, selectors), newImportSymbol(ctx.owner, expr)) + def Export(expr: Tree, selectors: List[untpd.ImportSelector])(using Context): Export = + ta.assignType(untpd.Export(expr, selectors)) + def PackageDef(pid: RefTree, stats: List[Tree])(using Context): PackageDef = ta.assignType(untpd.PackageDef(pid, stats), pid) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index e13d5a4c3b2c..b98d7df11221 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -118,7 +118,6 @@ 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 Export(expr: Tree, selectors: List[ImportSelector])(implicit @constructorOnly src: SourceFile) extends Tree case class ExtMethods(tparams: List[TypeDef], vparamss: List[List[ValDef]], methods: List[DefDef])(implicit @constructorOnly src: SourceFile) extends Tree case class MacroTree(expr: Tree)(implicit @constructorOnly src: SourceFile) extends Tree @@ -409,6 +408,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { if (derived.isEmpty) new Template(constr, parents, self, body) else new DerivingTemplate(constr, parents ++ derived, self, body, derived.length) def Import(expr: Tree, selectors: List[ImportSelector])(implicit src: SourceFile): Import = new Import(expr, selectors) + def Export(expr: Tree, selectors: List[ImportSelector])(implicit src: SourceFile): Export = new Export(expr, selectors) def PackageDef(pid: RefTree, stats: List[Tree])(implicit src: SourceFile): PackageDef = new PackageDef(pid, stats) def Annotated(arg: Tree, annot: Tree)(implicit src: SourceFile): Annotated = new Annotated(arg, annot) @@ -633,10 +633,6 @@ 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 Export(tree: Tree)(expr: Tree, selectors: List[ImportSelector])(using Context): Tree = tree match { - case tree: Export if (expr eq tree.expr) && (selectors eq tree.selectors) => tree - case _ => finalize(tree, untpd.Export(expr, selectors)(tree.source)) - } def ExtMethods(tree: Tree)(tparams: List[TypeDef], vparamss: List[List[ValDef]], methods: List[DefDef])(using Context): Tree = tree match case tree: ExtMethods if (tparams eq tree.tparams) && (vparamss eq tree.vparamss) && (methods == tree.methods) => tree case _ => finalize(tree, untpd.ExtMethods(tparams, vparamss, methods)(tree.source)) @@ -704,8 +700,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { cpy.ContextBounds(tree)(transformSub(bounds), transform(cxBounds)) case PatDef(mods, pats, tpt, rhs) => cpy.PatDef(tree)(mods, transform(pats), transform(tpt), transform(rhs)) - case Export(expr, selectors) => - cpy.Export(tree)(transform(expr), selectors) case ExtMethods(tparams, vparamss, methods) => cpy.ExtMethods(tree)(transformSub(tparams), vparamss.mapConserve(transformSub(_)), transformSub(methods)) case ImportSelector(imported, renamed, bound) => @@ -765,8 +759,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { this(this(x, bounds), cxBounds) case PatDef(mods, pats, tpt, rhs) => this(this(this(x, pats), tpt), rhs) - case Export(expr, _) => - this(x, expr) case ExtMethods(tparams, vparamss, methods) => this(vparamss.foldLeft(this(x, tparams))(apply), methods) case ImportSelector(imported, renamed, bound) => diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 49faeff87ac4..80a9a026146a 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4815,6 +4815,11 @@ object Types { /** The type of an import clause tree */ case class ImportType(expr: Tree) extends UncachedGroundType + /** Sentinal for typed export clauses */ + @sharable case object ExportType extends CachedGroundType { + override def computeHash(bs: Binders): Int = hashSeed + } + /** Sentinel for "missing type" */ @sharable case object NoType extends CachedGroundType { override def computeHash(bs: Binders): Int = hashSeed diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index eaad63ad5332..08fde74149b4 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -593,6 +593,12 @@ class TreePickler(pickler: TastyPickler) { pickleTree(expr) pickleSelectors(selectors) } + case Export(expr, selectors) => + writeByte(EXPORT) + withLength { + pickleTree(expr) + pickleSelectors(selectors) + } case PackageDef(pid, stats) => writeByte(PACKAGE) withLength { pickleType(pid.tpe); pickleStats(stats) } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index de4c145e6632..b305560c77b0 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -711,7 +711,7 @@ class TreeUnpickler(reader: TastyReader, else if (sym.isClass || sym.is(Method, butNot = Deferred) && !sym.isConstructor) initsFlags &= NoInits - case IMPORT => + case IMPORT | EXPORT => skipTree() case PACKAGE => processPackage { (pid, end) => indexStats(end) } @@ -970,7 +970,9 @@ class TreeUnpickler(reader: TastyReader, case TYPEDEF | VALDEF | DEFDEF => readIndexedDef() case IMPORT => - readImport() + readImportOrExport(Import(_, _))() + case EXPORT => + readImportOrExport(Export(_, _))() case PACKAGE => val start = currentAddr processPackage { (pid, end) => @@ -980,14 +982,16 @@ class TreeUnpickler(reader: TastyReader, readTerm()(using ctx.withOwner(exprOwner)) } - def readImport()(using Context): Tree = { + inline def readImportOrExport(inline mkTree: + (Tree, List[untpd.ImportSelector]) => Tree)()(using Context): Tree = { val start = currentAddr assert(sourcePathAt(start).isEmpty) readByte() readEnd() val expr = readTerm() - setSpan(start, Import(expr, readSelectors())) + setSpan(start, mkTree(expr, readSelectors())) } + def readSelectors()(using Context): List[untpd.ImportSelector] = if nextByte == IMPORTED then val start = currentAddr diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 95d819065ba4..f57caa23bb3c 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3747,7 +3747,7 @@ object Parsers { else if (in.token == IMPORT) stats ++= importClause(IMPORT, mkImport(outermost)) else if (in.token == EXPORT) - stats ++= importClause(EXPORT, Export.apply) + stats ++= importClause(EXPORT, Export(_,_)) else if isIdent(nme.extension) && followingIsExtension() then stats += extension() else if isDefIntro(modifierTokens) then @@ -3801,7 +3801,7 @@ object Parsers { if (in.token == IMPORT) stats ++= importClause(IMPORT, mkImport()) else if (in.token == EXPORT) - stats ++= importClause(EXPORT, Export.apply) + stats ++= importClause(EXPORT, Export(_,_)) else if isIdent(nme.extension) && followingIsExtension() then stats += extension() else if (isDefIntro(modifierTokensOrCase)) diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala index cd9860d3710c..52e7cb2a6633 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala @@ -328,15 +328,16 @@ private class ExtractDependenciesCollector extends tpd.TreeTraverser { thisTreeT private def addInheritanceDependencies(tree: Template)(using Context): Unit = if (tree.parents.nonEmpty) { - val depContext = - if (tree.symbol.owner.isLocal) LocalDependencyByInheritance - else DependencyByInheritance + val depContext = depContextOf(tree.symbol.owner) val from = resolveDependencySource - tree.parents.foreach { parent => + for parent <- tree.parents do _dependencies += ClassDependency(from, parent.tpe.classSymbol, depContext) - } } + private def depContextOf(cls: Symbol)(using Context): DependencyContext = + if cls.isLocal then LocalDependencyByInheritance + else DependencyByInheritance + private def ignoreDependency(sym: Symbol)(using Context) = !sym.exists || sym.isAbsent(canForce = false) || // ignore dependencies that have a symbol but do not exist. @@ -364,6 +365,20 @@ private class ExtractDependenciesCollector extends tpd.TreeTraverser { thisTreeT addImported(sel.name) if sel.rename != sel.name then addUsedName(sel.rename, UseScope.Default) + case exp @ Export(expr, selectors) => + val dep = expr.tpe.classSymbol + if dep.exists && selectors.exists(_.isWildcard) then + // If an export is a wildcard, that means that the enclosing class + // has forwarders to all the applicable signatures in `dep`, + // those forwarders will cause member/type ref dependencies to be + // recorded. However, if `dep` adds more members with new names, + // there has been no record that the enclosing class needs to + // recompile to capture the new members. We add an + // inheritance dependency in the presence of wildcard exports + // to ensure all new members of `dep` are forwarded to. + val depContext = depContextOf(ctx.owner.lexicallyEnclosingClass) + val from = resolveDependencySource + _dependencies += ClassDependency(from, dep, depContext) case t: TypeTree => addTypeDependency(t.tpe) case ref: RefTree => diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index 643f62933629..eb1f4816236e 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -152,7 +152,7 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => } override def transformOther(tree: Tree)(using Context): Tree = tree match { - case tree: Import => EmptyTree + case tree: ImportOrExport[_] => EmptyTree case tree: NamedArg => transformAllDeep(tree.arg) case tree => if (tree.isType) toTypeTree(tree) else tree } diff --git a/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala b/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala index 703369234d4a..f678a1930758 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala @@ -140,7 +140,7 @@ abstract class TreeMapWithStages(@constructorOnly ictx: Context) extends TreeMap tpd.patVars(pat).foreach(markSymbol) mapOverTree(last) - case _: Import => + case (_:Import | _:Export) => tree case _ => @@ -161,4 +161,4 @@ object TreeMapWithStages { def freshStagingContext(using Context): Context = ctx.fresh.setProperty(LevelOfKey, new mutable.HashMap[Symbol, Int]) -} \ No newline at end of file +} diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index b58e05e1129d..2c3d1ea80347 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -511,6 +511,9 @@ trait TypeAssigner { def assignType(tree: untpd.Import, sym: Symbol)(using Context): Import = tree.withType(sym.termRef) + def assignType(tree: untpd.Export)(using Context): Export = + tree.withType(ExportType) + def assignType(tree: untpd.Annotated, arg: Tree, annot: Tree)(using Context): Annotated = { assert(tree.isType) // annotating a term is done via a Typed node, can't use Annotate directly tree.withType(AnnotatedType(arg.tpe, Annotation(annot))) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 2bab50b763e3..df85a4f1c5a4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2235,7 +2235,8 @@ class Typer extends Namer def localDummy(cls: ClassSymbol, impl: untpd.Template)(using Context): Symbol = newLocalDummy(cls, impl.span) - def typedImport(imp: untpd.Import, sym: Symbol)(using Context): Import = { + inline def typedImportOrExport[T <: ImportOrExport[Untyped]](imp: T)( + inline mkTree: (Tree, List[untpd.ImportSelector]) => imp.ThisTree[Type])(using Context): imp.ThisTree[Type] = { val expr1 = typedExpr(imp.expr, AnySelectionProto) checkLegalImportPath(expr1) val selectors1: List[untpd.ImportSelector] = imp.selectors.mapConserve { sel => @@ -2244,7 +2245,19 @@ class Typer extends Namer sel.imported, sel.renamed, untpd.TypedSplice(typedType(sel.bound))) .asInstanceOf[untpd.ImportSelector] } - assignType(cpy.Import(imp)(expr1, selectors1), sym) + mkTree(expr1, selectors1) + } + + def typedImport(imp: untpd.Import, sym: Symbol)(using Context): Import = { + typedImportOrExport(imp)((expr1, selectors1) => + assignType(cpy.Import(imp)(expr1, selectors1), sym) + ) + } + + def typedExport(exp: untpd.Export)(using Context): Export = { + typedImportOrExport(exp)((expr1, selectors1) => + assignType(cpy.Export(exp)(expr1, selectors1)) + ) } def typedPackageDef(tree: untpd.PackageDef)(using Context): Tree = @@ -2481,6 +2494,7 @@ class Typer 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.Match => typedMatch(tree, pt) case tree: untpd.Return => typedReturn(tree) case tree: untpd.WhileDo => typedWhileDo(tree) @@ -2643,6 +2657,7 @@ class Typer extends Namer case Thicket(stats) :: rest => traverse(stats ::: rest) case (stat: untpd.Export) :: rest => + buf += typed(stat) buf ++= stat.attachmentOrElse(ExportForwarders, Nil) // no attachment can happen in case of cyclic references traverse(rest) diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 5455c8b3f943..d4ffeae0cf62 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -157,21 +157,41 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler end ImportTypeTestImpl object Import extends ImportModule: - def apply(expr: Term, selectors: List[ImportSelector]): Import = + def apply(expr: Term, selectors: List[Selector]): Import = withDefaultPos(tpd.Import(expr, selectors)) - def copy(original: Tree)(expr: Term, selectors: List[ImportSelector]): Import = + def copy(original: Tree)(expr: Term, selectors: List[Selector]): Import = tpd.cpy.Import(original)(expr, selectors) - def unapply(tree: Import): Option[(Term, List[ImportSelector])] = + def unapply(tree: Import): Option[(Term, List[Selector])] = Some((tree.expr, tree.selectors)) end Import object ImportMethodsImpl extends ImportMethods: extension (self: Import): def expr: Term = self.expr - def selectors: List[ImportSelector] = self.selectors + def selectors: List[Selector] = self.selectors end extension end ImportMethodsImpl + type Export = tpd.Export + + object ExportTypeTest extends TypeTest[Tree, Export]: + def unapply(x: Tree): Option[Export & x.type] = x match + case tree: (tpd.Export & x.type) => Some(tree) + case _ => None + end ExportTypeTest + + object Export extends ExportModule: + def unapply(tree: Export): Option[(Term, List[Selector])] = + Some((tree.expr, tree.selectors)) + end Export + + object ExportMethodsImpl extends ExportMethods: + extension (self: Export): + def expr: Term = self.expr + def selectors: List[Selector] = self.selectors + end extension + end ExportMethodsImpl + type Statement = tpd.Tree object StatementTypeTestImpl extends TypeTest[Tree, Statement]: @@ -1468,14 +1488,14 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler end extension end AlternativesMethodsImpl - type ImportSelector = untpd.ImportSelector + type Selector = untpd.ImportSelector - object ImportSelector extends ImportSelectorModule + object Selector extends SelectorModule type SimpleSelector = untpd.ImportSelector - object SimpleSelectorTypeTestImpl extends TypeTest[ImportSelector, SimpleSelector]: - def unapply(x: ImportSelector): Option[SimpleSelector & x.type] = x match + object SimpleSelectorTypeTestImpl extends TypeTest[Selector, SimpleSelector]: + def unapply(x: Selector): Option[SimpleSelector & x.type] = x match case x: (untpd.ImportSelector & x.type) if x.renamed.isEmpty && !x.isGiven => Some(x) case _ => None // TODO: handle import bounds end SimpleSelectorTypeTestImpl @@ -1484,7 +1504,6 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def unapply(x: SimpleSelector): Option[String] = Some(x.name.toString) end SimpleSelector - object SimpleSelectorMethodsImpl extends SimpleSelectorMethods: extension (self: SimpleSelector): def name: String = self.imported.name.toString @@ -1494,8 +1513,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler type RenameSelector = untpd.ImportSelector - object RenameSelectorTypeTestImpl extends TypeTest[ImportSelector, RenameSelector]: - def unapply(x: ImportSelector): Option[RenameSelector & x.type] = x match + object RenameSelectorTypeTestImpl extends TypeTest[Selector, RenameSelector]: + def unapply(x: Selector): Option[RenameSelector & x.type] = x match case x: (untpd.ImportSelector & x.type) if !x.renamed.isEmpty => Some(x) case _ => None end RenameSelectorTypeTestImpl @@ -1515,8 +1534,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler type OmitSelector = untpd.ImportSelector - object OmitSelectorTypeTestImpl extends TypeTest[ImportSelector, OmitSelector]: - def unapply(x: ImportSelector): Option[OmitSelector & x.type] = x match { + object OmitSelectorTypeTestImpl extends TypeTest[Selector, OmitSelector]: + def unapply(x: Selector): Option[OmitSelector & x.type] = x match { case self: (untpd.ImportSelector & x.type) => self.renamed match case dotc.ast.Trees.Ident(nme.WILDCARD) => Some(self) @@ -1538,8 +1557,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler type GivenSelector = untpd.ImportSelector - object GivenSelectorTypeTestImpl extends TypeTest[ImportSelector, GivenSelector]: - def unapply(x: ImportSelector): Option[GivenSelector & x.type] = x match { + object GivenSelectorTypeTestImpl extends TypeTest[Selector, GivenSelector]: + def unapply(x: Selector): Option[GivenSelector & x.type] = x match { case self: (untpd.ImportSelector & x.type) if x.isGiven => Some(self) case _ => None } diff --git a/compiler/src/scala/quoted/runtime/impl/printers/Extractors.scala b/compiler/src/scala/quoted/runtime/impl/printers/Extractors.scala index a4f137fea4af..db4e7821df71 100644 --- a/compiler/src/scala/quoted/runtime/impl/printers/Extractors.scala +++ b/compiler/src/scala/quoted/runtime/impl/printers/Extractors.scala @@ -129,6 +129,8 @@ object Extractors { this += ", " += self += ", " ++= body += ")" case Import(expr, selectors) => this += "Import(" += expr += ", " ++= selectors += ")" + case Export(expr, selectors) => + this += "Export(" += expr += ", " ++= selectors += ")" case PackageClause(pid, stats) => this += "PackageClause(" += pid += ", " ++= stats += ")" case Inferred() => @@ -239,7 +241,7 @@ object Extractors { this += "Signature(" ++= params.map(_.toString) += ", " += res += ")" } - def visitImportSelector(sel: ImportSelector): this.type = sel match { + def visitSelector(sel: Selector): this.type = sel match { case SimpleSelector(id) => this += "SimpleSelector(" += id += ")" case RenameSelector(id1, id2) => this += "RenameSelector(" += id1 += ", " += id2 += ")" case OmitSelector(id) => this += "OmitSelector(" += id += ")" @@ -291,8 +293,8 @@ object Extractors { def +=(x: Option[Signature]): self.type = { visitOption(x, visitSignature); buff } } - private implicit class ImportSelectorOps(buff: self.type) { - def ++=(x: List[ImportSelector]): self.type = { visitList(x, visitImportSelector); buff } + private implicit class SelectorOps(buff: self.type) { + def ++=(x: List[Selector]): self.type = { visitList(x, visitSelector); buff } } private implicit class SymbolOps(buff: self.type) { diff --git a/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala b/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala index 8522b8f268b1..92d37e12ed89 100644 --- a/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala +++ b/compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala @@ -115,7 +115,7 @@ object SourceCode { val stats1 = stats.collect { case stat: PackageClause => stat case stat: Definition if !(stat.symbol.flags.is(Flags.Module) && stat.symbol.flags.is(Flags.Lazy)) => stat - case stat @ Import(_, _) => stat + case stat @ (_:Import | _:Export) => stat } name match { case Ident("") => @@ -130,7 +130,13 @@ object SourceCode { this += "import " printTree(expr) this += "." - printImportSelectors(selectors) + printSelectors(selectors) + + case Export(expr, selectors) => + this += "export " + printTree(expr) + this += "." + printSelectors(selectors) case cdef @ ClassDef(name, DefDef(_, targs, argss, _, _), parents, derived, self, stats) => printDefAnnotations(cdef) @@ -220,7 +226,7 @@ object SourceCode { } val stats1 = stats.collect { case stat: Definition if keepDefinition(stat) => stat - case stat @ Import(_, _) => stat + case stat @ (_:Import | _:Export) => stat case stat: Term => stat } @@ -670,12 +676,12 @@ object SourceCode { this } - private def printImportSelectors(selectors: List[ImportSelector]): this.type = { - def printSeparated(list: List[ImportSelector]): Unit = list match { + private def printSelectors(selectors: List[Selector]): this.type = { + def printSeparated(list: List[Selector]): Unit = list match { case Nil => - case x :: Nil => printImportSelector(x) + case x :: Nil => printSelector(x) case x :: xs => - printImportSelector(x) + printSelector(x) this += ", " printSeparated(xs) } @@ -1234,7 +1240,7 @@ object SourceCode { throw new MatchError(tpe.showExtractors) } - private def printImportSelector(sel: ImportSelector): this.type = sel match { + private def printSelector(sel: Selector): this.type = sel match { case SimpleSelector(name) => this += name case OmitSelector(name) => this += name += " => _" case RenameSelector(name, newName) => this += name += " => " += newName diff --git a/library/src/scala/quoted/ExprMap.scala b/library/src/scala/quoted/ExprMap.scala index ac3e93fb18e5..bc4e167fb431 100644 --- a/library/src/scala/quoted/ExprMap.scala +++ b/library/src/scala/quoted/ExprMap.scala @@ -16,7 +16,7 @@ trait ExprMap: transformTerm(tree, TypeRepr.of[Any])(owner) case tree: Definition => transformDefinition(tree)(owner) - case tree: Import => + case tree @ (_:Import | _:Export) => tree } } diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 2f90f26913b4..2f1adf48d10b 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -81,8 +81,10 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * ```none * * +- Tree -+- PackageClause - * +- Import - * +- Statement -+- Definition --+- ClassDef + * | + * +- Statement -+- Import + * | +- Export + * | +- Definition --+- ClassDef * | | +- TypeDef * | | +- DefDef * | | +- ValDef @@ -158,10 +160,10 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * +- TypeBounds * +- NoPrefix * - * +- ImportSelector -+- SimpleSelector - * +- RenameSelector - * +- OmitSelector - * +- GivenSelector + * +- Selector -+- SimpleSelector + * +- RenameSelector + * +- OmitSelector + * +- GivenSelector * * +- Signature * @@ -291,9 +293,9 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Methods of the module object `val Import` */ trait ImportModule { this: Import.type => - def apply(expr: Term, selectors: List[ImportSelector]): Import - def copy(original: Tree)(expr: Term, selectors: List[ImportSelector]): Import - def unapply(tree: Import): Option[(Term, List[ImportSelector])] + def apply(expr: Term, selectors: List[Selector]): Import + def copy(original: Tree)(expr: Term, selectors: List[Selector]): Import + def unapply(tree: Import): Option[(Term, List[Selector])] } /** Makes extension methods on `Import` available without any imports */ @@ -306,10 +308,34 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => trait ImportMethods: extension (self: Import): def expr: Term - def selectors: List[ImportSelector] + def selectors: List[Selector] end extension end ImportMethods + /** Tree representing an export clause in the source code. + * Export forwarders generated from this clause appear in the same scope. + */ + type Export <: Statement + + given TypeTest[Tree, Export] = ExportTypeTest + protected val ExportTypeTest: TypeTest[Tree, Export] + + val Export: ExportModule + + trait ExportModule { this: Export.type => + def unapply(tree: Export): Option[(Term, List[Selector])] + } + + given ExportMethods: ExportMethods = ExportMethodsImpl + protected val ExportMethodsImpl: ExportMethods + + trait ExportMethods: + extension (self: Export): + def expr: Term + def selectors: List[Selector] + end extension + end ExportMethods + /** Tree representing a statement in the source code */ type Statement <: Tree @@ -2193,31 +2219,31 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => end AlternativesMethods ////////////////////// - // IMPORT SELECTORS // + // SELECTORS // ///////////////////// - /** Import selectors: + /** Import/Export selectors: * * SimpleSelector: `.bar` in `import foo.bar` - * * RenameSelector: `.{bar => baz}` in `import foo.{bar => baz}` + * * RenameSelector: `.{bar => baz}` in `export foo.{bar => baz}` * * OmitSelector: `.{bar => _}` in `import foo.{bar => _}` - * * GivneSelector: `.given`/`.{given T}` in `import foo.given`/`import foo.{given T}` + * * GivenSelector: `.given`/`.{given T}` in `export foo.given`/`import foo.{given T}` */ - type ImportSelector <: AnyRef + type Selector <: AnyRef - /** Module object of `type ImportSelector` */ - val ImportSelector: ImportSelectorModule + /** Module object of `type Selector` */ + val Selector: SelectorModule - /** Methods of the module object `val ImportSelector` */ - trait ImportSelectorModule { this: ImportSelector.type => } + /** Methods of the module object `val Selector` */ + trait SelectorModule { this: Selector.type => } - /** Simple import selector: `.bar` in `import foo.bar` */ - type SimpleSelector <: ImportSelector + /** Simple import/export selector: `.bar` in `import foo.bar` */ + type SimpleSelector <: Selector - /** `TypeTest` that allows testing at runtime in a pattern match if an `ImportSelector` is a `SimpleSelector` */ - given SimpleSelectorTypeTest: TypeTest[ImportSelector, SimpleSelector] = SimpleSelectorTypeTestImpl + /** `TypeTest` that allows testing at runtime in a pattern match if a `Selector` is a `SimpleSelector` */ + given SimpleSelectorTypeTest: TypeTest[Selector, SimpleSelector] = SimpleSelectorTypeTestImpl - /** Implementation of `SimpleSelectorTypeTest` */ - protected val SimpleSelectorTypeTestImpl: TypeTest[ImportSelector, SimpleSelector] + /** Implementation of `TypeTest[Selector, SimpleSelector]` */ + protected val SimpleSelectorTypeTestImpl: TypeTest[Selector, SimpleSelector] /** Module object of `type SimpleSelector` */ val SimpleSelector: SimpleSelectorModule @@ -2241,14 +2267,14 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => end extension end SimpleSelectorMethods - /** Rename import selector: `.{bar => baz}` in `import foo.{bar => baz}` */ - type RenameSelector <: ImportSelector + /** Rename import/export selector: `.{bar => baz}` in `import foo.{bar => baz}` */ + type RenameSelector <: Selector - /** `TypeTest` that allows testing at runtime in a pattern match if an `ImportSelector` is a `RenameSelector` */ - given RenameSelectorTypeTest: TypeTest[ImportSelector, RenameSelector] = RenameSelectorTypeTestImpl + /** `TypeTest` that allows testing at runtime in a pattern match if a `Selector` is a `RenameSelector` */ + given RenameSelectorTypeTest: TypeTest[Selector, RenameSelector] = RenameSelectorTypeTestImpl - /** Implementation of `RenameSelectorTypeTest` */ - protected val RenameSelectorTypeTestImpl: TypeTest[ImportSelector, RenameSelector] + /** Implementation of `TypeTest[Selector, RenameSelector]` */ + protected val RenameSelectorTypeTestImpl: TypeTest[Selector, RenameSelector] /** Module object of `type RenameSelector` */ val RenameSelector: RenameSelectorModule @@ -2274,14 +2300,14 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => end extension end RenameSelectorMethods - /** Omit import selector: `.{bar => _}` in `import foo.{bar => _}` */ - type OmitSelector <: ImportSelector + /** Omit import/export selector: `.{bar => _}` in `import foo.{bar => _}` */ + type OmitSelector <: Selector - /** `TypeTest` that allows testing at runtime in a pattern match if an `ImportSelector` is an `OmitSelector` */ - given OmitSelectorTypeTest: TypeTest[ImportSelector, OmitSelector] = OmitSelectorTypeTestImpl + /** `TypeTest` that allows testing at runtime in a pattern match if a `Selector` is an `OmitSelector` */ + given OmitSelectorTypeTest: TypeTest[Selector, OmitSelector] = OmitSelectorTypeTestImpl - /** Implementation of `OmitSelectorTypeTest` */ - protected val OmitSelectorTypeTestImpl: TypeTest[ImportSelector, OmitSelector] + /** Implementation of `TypeTest[Selector, OmitSelector]` */ + protected val OmitSelectorTypeTestImpl: TypeTest[Selector, OmitSelector] /** Module object of `type OmitSelector` */ val OmitSelector: OmitSelectorModule @@ -2304,14 +2330,14 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => def namePos: Position end OmitSelectorMethods - /** Omit import selector: `.given`/`.{given T}` in `import foo.given`/`import foo.{given T}` */ - type GivenSelector <: ImportSelector + /** given import/export selector: `.given`/`.{given T}` in `import foo.given`/`export foo.{given T}` */ + type GivenSelector <: Selector - /** `TypeTest` that allows testing at runtime in a pattern match if an `ImportSelector` is a `GivenSelector` */ - given GivenSelectorTypeTest: TypeTest[ImportSelector, GivenSelector] = GivenSelectorTypeTestImpl + /** `TypeTest` that allows testing at runtime in a pattern match if an `Selector` is a `GivenSelector` */ + given GivenSelectorTypeTest: TypeTest[Selector, GivenSelector] = GivenSelectorTypeTestImpl - /** Implementation of `GivenSelectorTypeTest` */ - protected val GivenSelectorTypeTestImpl: TypeTest[ImportSelector, GivenSelector] + /** Implementation of `TypeTest[Selector, GivenSelector]` */ + protected val GivenSelectorTypeTestImpl: TypeTest[Selector, GivenSelector] /** Module object of `type GivenSelector` */ val GivenSelector: GivenSelectorModule @@ -4310,6 +4336,8 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => foldTrees(foldTrees(foldTrees(foldTrees(foldTree(x, constr)(owner), parents)(owner), derived)(owner), self)(owner), body)(owner) case Import(expr, _) => foldTree(x, expr)(owner) + case Export(expr, _) => + foldTree(x, expr)(owner) case clause @ PackageClause(pid, stats) => foldTrees(foldTree(x, pid)(owner), stats)(clause.symbol) case Inferred() => x @@ -4376,6 +4404,8 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => PackageClause.copy(tree)(transformTerm(tree.pid).asInstanceOf[Ref], transformTrees(tree.stats)(tree.symbol)) case tree: Import => Import.copy(tree)(transformTerm(tree.expr)(owner), tree.selectors) + case tree: Export => + tree case tree: Statement => transformStatement(tree)(owner) case tree: TypeTree => transformTypeTree(tree)(owner) @@ -4414,6 +4444,8 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => ClassDef.copy(tree)(tree.name, tree.constructor, tree.parents, tree.derived, tree.self, tree.body) case tree: Import => Import.copy(tree)(transformTerm(tree.expr)(owner), tree.selectors) + case tree: Export => + tree } } diff --git a/sbt-dotty/sbt-test/source-dependencies/export-clauses/A.scala b/sbt-dotty/sbt-test/source-dependencies/export-clauses/A.scala new file mode 100644 index 000000000000..b73afea0e560 --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/export-clauses/A.scala @@ -0,0 +1,18 @@ +class A { + + private val b: B = new B + export b._ + + class Inner { + private val c: C = new C + export c._ + } + + def local = { + class Local { + private val d: D = new D + export d._ + } + } + +} diff --git a/sbt-dotty/sbt-test/source-dependencies/export-clauses/B.scala b/sbt-dotty/sbt-test/source-dependencies/export-clauses/B.scala new file mode 100644 index 000000000000..49bd4cd0dbf0 --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/export-clauses/B.scala @@ -0,0 +1,3 @@ +class B { + val x: Int = 23 +} diff --git a/sbt-dotty/sbt-test/source-dependencies/export-clauses/C.scala b/sbt-dotty/sbt-test/source-dependencies/export-clauses/C.scala new file mode 100644 index 000000000000..88e383dfc4b8 --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/export-clauses/C.scala @@ -0,0 +1,3 @@ +class C { + val y: Int = 47 +} diff --git a/sbt-dotty/sbt-test/source-dependencies/export-clauses/D.scala b/sbt-dotty/sbt-test/source-dependencies/export-clauses/D.scala new file mode 100644 index 000000000000..0a541b6f40e8 --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/export-clauses/D.scala @@ -0,0 +1,3 @@ +class D { + val z: Int = 113 +} diff --git a/sbt-dotty/sbt-test/source-dependencies/export-clauses/build.sbt b/sbt-dotty/sbt-test/source-dependencies/export-clauses/build.sbt new file mode 100644 index 000000000000..1036709ccfc1 --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/export-clauses/build.sbt @@ -0,0 +1,25 @@ +import sbt.internal.inc.Analysis +import complete.DefaultParsers._ + +// Reset compiler iterations, necessary because tests run in batch mode +val recordPreviousIterations = taskKey[Unit]("Record previous iterations.") +recordPreviousIterations := { + val log = streams.value.log + CompileState.previousIterations = { + val previousAnalysis = (previousCompile in Compile).value.analysis.asScala + previousAnalysis match { + case None => + log.info("No previous analysis detected") + 0 + case Some(a: Analysis) => a.compilations.allCompilations.size + } + } +} + +val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.") + +checkIterations := { + val expected: Int = (Space ~> NatBasic).parsed + val actual: Int = ((compile in Compile).value match { case a: Analysis => a.compilations.allCompilations.size }) - CompileState.previousIterations + assert(expected == actual, s"Expected $expected compilations, got $actual") +} diff --git a/sbt-dotty/sbt-test/source-dependencies/export-clauses/changes/B1.scala b/sbt-dotty/sbt-test/source-dependencies/export-clauses/changes/B1.scala new file mode 100644 index 000000000000..f005f7348dc6 --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/export-clauses/changes/B1.scala @@ -0,0 +1,4 @@ +class B { + val x: Int = 23 + val a: Int = 31 +} diff --git a/sbt-dotty/sbt-test/source-dependencies/export-clauses/changes/C1.scala b/sbt-dotty/sbt-test/source-dependencies/export-clauses/changes/C1.scala new file mode 100644 index 000000000000..f07947f2d7d7 --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/export-clauses/changes/C1.scala @@ -0,0 +1,4 @@ +class C { + val y: Int = 47 + val b: Int = 93 +} diff --git a/sbt-dotty/sbt-test/source-dependencies/export-clauses/changes/D1.scala b/sbt-dotty/sbt-test/source-dependencies/export-clauses/changes/D1.scala new file mode 100644 index 000000000000..b7c4024928cd --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/export-clauses/changes/D1.scala @@ -0,0 +1,4 @@ +class D { + val z: Int = 113 + val c: Int = 271 +} diff --git a/sbt-dotty/sbt-test/source-dependencies/export-clauses/project/CompileState.scala b/sbt-dotty/sbt-test/source-dependencies/export-clauses/project/CompileState.scala new file mode 100644 index 000000000000..078db9c7bf56 --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/export-clauses/project/CompileState.scala @@ -0,0 +1,4 @@ +// This is necessary because tests are run in batch mode +object CompileState { + @volatile var previousIterations: Int = -1 +} diff --git a/sbt-dotty/sbt-test/source-dependencies/export-clauses/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/export-clauses/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..69f15d168bfc --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/export-clauses/project/DottyInjectedPlugin.scala @@ -0,0 +1,12 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion"), + scalacOptions += "-source:3.0-migration" + ) +} diff --git a/sbt-dotty/sbt-test/source-dependencies/export-clauses/project/plugins.sbt b/sbt-dotty/sbt-test/source-dependencies/export-clauses/project/plugins.sbt new file mode 100644 index 000000000000..c17caab2d98c --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/export-clauses/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % sys.props("plugin.version")) diff --git a/sbt-dotty/sbt-test/source-dependencies/export-clauses/test b/sbt-dotty/sbt-test/source-dependencies/export-clauses/test new file mode 100644 index 000000000000..343895225e04 --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/export-clauses/test @@ -0,0 +1,29 @@ +> recordPreviousIterations +> compile +$ copy-file changes/B1.scala B.scala +> compile +# This should be 3 because of: +# 1. the first `compile` call +# 2. the recompilation of B.scala +# 3. this recompilation triggering a recompilation of A.scala +# because B has a new member and A does a wildcard export +# from a value of type B. +> checkIterations 3 +$ copy-file changes/C1.scala C.scala +> compile +# This should be 5 because of: +# 1. the three prior compilations +# 2. the recompilation of C.scala +# 3. this recompilation triggering a recompilation of A.scala +# because C has a new member and A.Inner does a wildcard export +# from a value of type C. +> checkIterations 5 +$ copy-file changes/D1.scala D.scala +> compile +# This should be 7 because of: +# 1. the 5 prior compilations +# 2. the recompilation of D.scala +# 3. this recompilation triggering a recompilation of A.scala +# because D has a new member and A.local().Local does a wildcard export +# from a value of type D. +> checkIterations 7 diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index 23bbb6391cdf..2be4fb931341 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -60,6 +60,7 @@ Standard-Section: "ASTs" TopLevelStat* ValOrDefDef TYPEDEF Length NameRef (type_Term | Template) Modifier* -- modifiers type name (= type | bounds) | modifiers class name template IMPORT Length qual_Term Selector* -- import qual selectors + EXPORT Length qual_Term Selector* -- export qual selectors ValOrDefDef = VALDEF Length NameRef type_Term rhs_Term? Modifier* -- modifiers val name : type (= rhs)? DEFDEF Length NameRef TypeParam* Params* returnType_Term rhs_Term? Modifier* -- modifiers def name [typeparams] paramss : returnType (= rhs)? @@ -475,6 +476,7 @@ object TastyFormat { final val TERMREFin = 174 final val TYPEREFin = 175 final val SELECTin = 176 + final val EXPORT = 177 final val METHODtype = 180 @@ -634,6 +636,7 @@ object TastyFormat { case DEFDEF => "DEFDEF" case TYPEDEF => "TYPEDEF" case IMPORT => "IMPORT" + case EXPORT => "EXPORT" case TYPEPARAM => "TYPEPARAM" case PARAM => "PARAM" case IMPORTED => "IMPORTED" diff --git a/tests/run-macros/exports.check b/tests/run-macros/exports.check new file mode 100644 index 000000000000..5e124f8b8657 --- /dev/null +++ b/tests/run-macros/exports.check @@ -0,0 +1,16 @@ +visited exports with ExprMap +visited exports with TreeMap +extracted with TreeAccumulator: '{export Messages.{logMessage => log}}, '{export Messages.{count}} +reflection show: +{ + lazy val Observer: Observer = new Observer() + object Observer { + export Messages.{count} + final def count: scala.Int = Messages.count + } + () +} +reflection show extractors: +Inlined(None, Nil, Block(List(ValDef("Observer", TypeIdent("Observer$"), Some(Apply(Select(New(TypeIdent("Observer$")), ""), Nil))), ClassDef("Observer$", DefDef("", Nil, List(Nil), Inferred(), None), List(Apply(Select(New(Inferred()), ""), Nil)), Nil, Some(ValDef("_", Singleton(Ident("Observer")), None)), List(Export(Ident("Messages"), List(SimpleSelector(count))), DefDef("count", Nil, Nil, Inferred(), Some(Select(Ident("Messages"), "count")))))), Literal(Constant.Unit()))) +visited exports with splice +visited exports with splice inverted diff --git a/tests/run-macros/exports/Logger_1.scala b/tests/run-macros/exports/Logger_1.scala new file mode 100644 index 000000000000..a5fb1dad1990 --- /dev/null +++ b/tests/run-macros/exports/Logger_1.scala @@ -0,0 +1,3 @@ +trait Logger { + def log(a: String): Unit +} diff --git a/tests/run-macros/exports/Macro_2.scala b/tests/run-macros/exports/Macro_2.scala new file mode 100644 index 000000000000..596f611f36dc --- /dev/null +++ b/tests/run-macros/exports/Macro_2.scala @@ -0,0 +1,58 @@ +import scala.quoted._ + +inline def visitExportsTreeAccumulator[T](inline x: T)(inline f: String => Any): Any = ${ traverseExportsImpl('x, 'f) } +inline def visitExportsTreeMap[T](inline x: T)(inline f: T => Any): Any = ${ visitExportsTreeMapImpl('x, 'f) } +inline def visitExportsExprMap[T](inline x: T)(inline f: T => Any): Any = ${ visitExportsExprMapImpl('x, 'f) } +inline def visitExportsShow[T](inline x: T): Any = ${ visitExportsShowImpl('x) } +inline def visitExportsShowExtract[T](inline x: T): Any = ${ visitExportsShowExtractImpl('x) } +inline def visitExportsSplice(inline l: Logger): Logger = ${ mixinLoggerImpl('l) } +inline def visitExportsSpliceInverse(inline op: Logger => Logger): Logger = ${ mixinLoggerInverseImpl('op) } + +private def visitExportsExprMapImpl[T: Type](e: Expr[T], f: Expr[T => Any])(using Quotes): Expr[Any] = + '{$f(${IdempotentExprMap.transform(e)})} + +private def visitExportsTreeMapImpl[T: Type](e: Expr[T], f: Expr[T => Any])(using Quotes): Expr[Any] = + import quotes.reflect._ + object m extends TreeMap + '{$f(${m.transformTerm(Term.of(e))(Symbol.spliceOwner).asExprOf})} + +private def visitExportsShowImpl[T: Type](e: Expr[T])(using Quotes): Expr[Any] = + import quotes.reflect._ + '{println(${Expr(Term.of(e).show)})} + +private def visitExportsShowExtractImpl[T: Type](e: Expr[T])(using Quotes): Expr[Any] = + import quotes.reflect._ + '{println(${Expr(Term.of(e).showExtractors)})} + +private object IdempotentExprMap extends ExprMap { + + def transform[T](e: Expr[T])(using Type[T])(using Quotes): Expr[T] = + transformChildren(e) + +} + +private def traverseExportsImpl(e: Expr[Any], f: Expr[String => Any])(using Quotes): Expr[Any] = { + import quotes.reflect._ + import collection.mutable + + object ExportAccumulator extends TreeAccumulator[mutable.Buffer[String]] { + def foldTree(x: mutable.Buffer[String], tree: Tree)(owner: Symbol): mutable.Buffer[String] = tree match { + case tree: Export => foldOverTree(x += s"'{${tree.show}}", tree)(owner) + case _ => foldOverTree(x, tree)(owner) + } + } + + val res = + ExportAccumulator.foldTree(mutable.Buffer.empty, Term.of(e))(Symbol.spliceOwner).mkString(", ") + + '{ $f(${Expr(res)}) } +} + +private def mixinLoggerImpl(l: Expr[Logger])(using Quotes): Expr[Logger] = + '{ new Logger { + private val delegate = $l + export delegate._ + }} + +private def mixinLoggerInverseImpl(op: Expr[Logger => Logger])(using Quotes): Expr[Logger] = + '{ $op(new Logger { def log(a: String): Unit = println(a) }) } diff --git a/tests/run-macros/exports/Test_3.scala b/tests/run-macros/exports/Test_3.scala new file mode 100644 index 000000000000..ce4192f051c0 --- /dev/null +++ b/tests/run-macros/exports/Test_3.scala @@ -0,0 +1,31 @@ +object Messages { + private var logs: Int = 0 + def logMessage(a: String): Unit = + logs += 1 + println(a) + def count = logs +} + +@main def Test: Unit = + assert(Messages.count == 0) + visitExportsExprMap(new Logger { export Messages.{logMessage => log} })( + _.log("visited exports with ExprMap") + ) + assert(Messages.count == 1) + visitExportsTreeMap(new Logger { export Messages.{logMessage => log} })( + _.log("visited exports with TreeMap") + ) + assert(Messages.count == 2) + visitExportsTreeAccumulator(new Logger { export Messages.{logMessage => log}; export Messages.count })( + exportStrings => println(s"extracted with TreeAccumulator: $exportStrings") + ) + println("reflection show:") + visitExportsShow({ object Observer { export Messages.count } }) + println("reflection show extractors:") + visitExportsShowExtract({ object Observer { export Messages.count } }) + val localLogger = new Logger { def log(a: String): Unit = println(a) } + visitExportsSplice(localLogger).log("visited exports with splice") + visitExportsSpliceInverse(logger => new Logger { + private val delegate = logger + export delegate._ + }).log("visited exports with splice inverted") diff --git a/tests/semanticdb/expect/exports-example-Codec.expect.scala b/tests/semanticdb/expect/exports-example-Codec.expect.scala new file mode 100644 index 000000000000..b2afc55a75f0 --- /dev/null +++ b/tests/semanticdb/expect/exports-example-Codec.expect.scala @@ -0,0 +1,15 @@ +package exports.example + +trait Decoder/*<-exports::example::Decoder#*/[+T/*<-exports::example::Decoder#[T]*/] { + def decode/*<-exports::example::Decoder#decode().*/(a/*<-exports::example::Decoder#decode().(a)*/: Array/*->scala::Array#*/[Byte/*->scala::Byte#*/]): T/*->exports::example::Decoder#[T]*/ +} + +trait Encoder/*<-exports::example::Encoder#*/[-T/*<-exports::example::Encoder#[T]*/] { + def encode/*<-exports::example::Encoder#encode().*/(t/*<-exports::example::Encoder#encode().(t)*/: T/*->exports::example::Encoder#[T]*/): Array/*->scala::Array#*/[Byte/*->scala::Byte#*/] +} + +trait Codec/*<-exports::example::Codec#*/[T/*<-exports::example::Codec#[T]*/](decode/*<-exports::example::Codec#decode.*/: Decoder/*->exports::example::Decoder#*/[T/*->exports::example::Codec#[T]*/], encode/*<-exports::example::Codec#encode.*/: Encoder/*->exports::example::Encoder#*/[T/*->exports::example::Codec#[T]*/]) + extends Decoder/*->exports::example::Decoder#*/[T/*->exports::example::Codec#[T]*/] with Encoder/*->exports::example::Encoder#*/[T/*->exports::example::Codec#[T]*/] { + export decode/*->exports::example::Codec#decode.*//*->exports::example::Decoder#decode().*//*->exports::example::Codec#decode().(a)*/./*<-exports::example::Codec#decode().*/_ + export encode/*->exports::example::Codec#encode.*//*->exports::example::Encoder#encode().*//*->exports::example::Codec#encode().(t)*/./*<-exports::example::Codec#encode().*/_ +} diff --git a/tests/semanticdb/expect/exports-example-Codec.scala b/tests/semanticdb/expect/exports-example-Codec.scala new file mode 100644 index 000000000000..3745a4582bef --- /dev/null +++ b/tests/semanticdb/expect/exports-example-Codec.scala @@ -0,0 +1,15 @@ +package exports.example + +trait Decoder[+T] { + def decode(a: Array[Byte]): T +} + +trait Encoder[-T] { + def encode(t: T): Array[Byte] +} + +trait Codec[T](decode: Decoder[T], encode: Encoder[T]) + extends Decoder[T] with Encoder[T] { + export decode._ + export encode._ +} diff --git a/tests/semanticdb/expect/exports-package.expect.scala b/tests/semanticdb/expect/exports-package.expect.scala new file mode 100644 index 000000000000..79c0f5b16e53 --- /dev/null +++ b/tests/semanticdb/expect/exports-package.expect.scala @@ -0,0 +1,3 @@ +package exports + +/*<-exports::`exports-package$package`.*/export example.{Decoder/*<-exports::`exports-package$package`.Decoder#*/, Encoder/*<-exports::`exports-package$package`.Encoder#*/, Codec/*<-exports::`exports-package$package`.Codec#*/} diff --git a/tests/semanticdb/expect/exports-package.scala b/tests/semanticdb/expect/exports-package.scala new file mode 100644 index 000000000000..7abd72be9372 --- /dev/null +++ b/tests/semanticdb/expect/exports-package.scala @@ -0,0 +1,3 @@ +package exports + +export example.{Decoder, Encoder, Codec} diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 06ae1f60b3f0..b93be61612b9 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -3019,6 +3019,106 @@ Occurrences: [4:18..4:21): Int -> scala/Int# [4:26..4:30): Unit -> scala/Unit# +expect/exports-example-Codec.scala +---------------------------------- + +Summary: +Schema => SemanticDB v4 +Uri => exports-example-Codec.scala +Text => empty +Language => Scala +Symbols => 21 entries +Occurrences => 39 entries + +Symbols: +exports/example/Codec# => trait Codec +exports/example/Codec#[T] => typeparam T +exports/example/Codec#``(). => primary ctor +exports/example/Codec#``().(decode) => param decode +exports/example/Codec#``().(encode) => param encode +exports/example/Codec#decode(). => final method decode +exports/example/Codec#decode().(a) => param a +exports/example/Codec#decode. => val method decode +exports/example/Codec#encode(). => final method encode +exports/example/Codec#encode().(t) => param t +exports/example/Codec#encode. => val method encode +exports/example/Decoder# => trait Decoder +exports/example/Decoder#[T] => covariant typeparam T +exports/example/Decoder#``(). => primary ctor +exports/example/Decoder#decode(). => abstract method decode +exports/example/Decoder#decode().(a) => param a +exports/example/Encoder# => trait Encoder +exports/example/Encoder#[T] => contravariant typeparam T +exports/example/Encoder#``(). => primary ctor +exports/example/Encoder#encode(). => abstract method encode +exports/example/Encoder#encode().(t) => param t + +Occurrences: +[0:8..0:15): exports -> exports/ +[0:16..0:23): example <- exports/example/ +[2:6..2:13): Decoder <- exports/example/Decoder# +[2:13..2:13): <- exports/example/Decoder#``(). +[2:15..2:16): T <- exports/example/Decoder#[T] +[3:6..3:12): decode <- exports/example/Decoder#decode(). +[3:13..3:14): a <- exports/example/Decoder#decode().(a) +[3:16..3:21): Array -> scala/Array# +[3:22..3:26): Byte -> scala/Byte# +[3:30..3:31): T -> exports/example/Decoder#[T] +[6:6..6:13): Encoder <- exports/example/Encoder# +[6:13..6:13): <- exports/example/Encoder#``(). +[6:15..6:16): T <- exports/example/Encoder#[T] +[7:6..7:12): encode <- exports/example/Encoder#encode(). +[7:13..7:14): t <- exports/example/Encoder#encode().(t) +[7:16..7:17): T -> exports/example/Encoder#[T] +[7:20..7:25): Array -> scala/Array# +[7:26..7:30): Byte -> scala/Byte# +[10:6..10:11): Codec <- exports/example/Codec# +[10:11..10:11): <- exports/example/Codec#``(). +[10:12..10:13): T <- exports/example/Codec#[T] +[10:15..10:21): decode <- exports/example/Codec#decode. +[10:23..10:30): Decoder -> exports/example/Decoder# +[10:31..10:32): T -> exports/example/Codec#[T] +[10:35..10:41): encode <- exports/example/Codec#encode. +[10:43..10:50): Encoder -> exports/example/Encoder# +[10:51..10:52): T -> exports/example/Codec#[T] +[11:10..11:17): Decoder -> exports/example/Decoder# +[11:18..11:19): T -> exports/example/Codec#[T] +[11:26..11:33): Encoder -> exports/example/Encoder# +[11:34..11:35): T -> exports/example/Codec#[T] +[12:9..12:15): decode -> exports/example/Codec#decode. +[12:15..12:15): -> exports/example/Decoder#decode(). +[12:15..12:15): -> exports/example/Codec#decode().(a) +[12:16..12:16): <- exports/example/Codec#decode(). +[13:9..13:15): encode -> exports/example/Codec#encode. +[13:15..13:15): -> exports/example/Encoder#encode(). +[13:15..13:15): -> exports/example/Codec#encode().(t) +[13:16..13:16): <- exports/example/Codec#encode(). + +expect/exports-package.scala +---------------------------- + +Summary: +Schema => SemanticDB v4 +Uri => exports-package.scala +Text => empty +Language => Scala +Symbols => 4 entries +Occurrences => 6 entries + +Symbols: +exports/`exports-package$package`. => final package object exports +exports/`exports-package$package`.Codec# => final type Codec +exports/`exports-package$package`.Decoder# => final type Decoder +exports/`exports-package$package`.Encoder# => final type Encoder + +Occurrences: +[0:8..0:15): exports <- exports/ +[2:0..2:0): <- exports/`exports-package$package`. +[2:7..2:14): example -> exports/example/ +[2:16..2:23): Decoder <- exports/`exports-package$package`.Decoder# +[2:25..2:32): Encoder <- exports/`exports-package$package`.Encoder# +[2:34..2:39): Codec <- exports/`exports-package$package`.Codec# + expect/filename with spaces.scala --------------------------------- From 31de5df95887bf81757e3fe26307f7f8ab49fe3e Mon Sep 17 00:00:00 2001 From: bishabosha Date: Tue, 1 Dec 2020 16:31:50 +0100 Subject: [PATCH 2/4] remove ExportType sentinal --- compiler/src/dotty/tools/dotc/core/Types.scala | 5 ----- compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 80a9a026146a..49faeff87ac4 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4815,11 +4815,6 @@ object Types { /** The type of an import clause tree */ case class ImportType(expr: Tree) extends UncachedGroundType - /** Sentinal for typed export clauses */ - @sharable case object ExportType extends CachedGroundType { - override def computeHash(bs: Binders): Int = hashSeed - } - /** Sentinel for "missing type" */ @sharable case object NoType extends CachedGroundType { override def computeHash(bs: Binders): Int = hashSeed diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 2c3d1ea80347..4d3cb79ea95e 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -512,7 +512,7 @@ trait TypeAssigner { tree.withType(sym.termRef) def assignType(tree: untpd.Export)(using Context): Export = - tree.withType(ExportType) + tree.withType(defn.UnitType) def assignType(tree: untpd.Annotated, arg: Tree, annot: Tree)(using Context): Annotated = { assert(tree.isType) // annotating a term is done via a Typed node, can't use Annotate directly From 666739bb9f392d76b039669a65605e8e5f602e90 Mon Sep 17 00:00:00 2001 From: bishabosha Date: Wed, 2 Dec 2020 14:42:43 +0100 Subject: [PATCH 3/4] restrict export wildcard from package --- .../src/dotty/tools/dotc/typer/Checking.scala | 23 ++++++++++++++++--- .../src/dotty/tools/dotc/typer/Namer.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 23 ++++++++----------- tests/neg/package-export/defaults.scala | 3 +++ tests/neg/package-export/enums.scala | 5 ++++ tests/neg/package-export/numerics.scala | 3 +++ tests/neg/package-export/package.scala | 7 ++++++ 7 files changed, 49 insertions(+), 17 deletions(-) create mode 100644 tests/neg/package-export/defaults.scala create mode 100644 tests/neg/package-export/enums.scala create mode 100644 tests/neg/package-export/numerics.scala create mode 100644 tests/neg/package-export/package.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 90d007074c83..f9d9fbeb84fa 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -719,12 +719,29 @@ trait Checking { recur(pat, pt) } - /** Check that `path` is a legal prefix for an import or export clause */ - def checkLegalImportPath(path: Tree)(using Context): Unit = { - checkStable(path.tpe, path.srcPos, "import prefix") + private def checkLegalImportOrExportPath(path: Tree, kind: String)(using Context): Unit = { + checkStable(path.tpe, path.srcPos, kind) if (!ctx.isAfterTyper) Checking.checkRealizable(path.tpe, path.srcPos) } + /** Check that `path` is a legal prefix for an import clause */ + def checkLegalImportPath(path: Tree)(using Context): Unit = { + checkLegalImportOrExportPath(path, "import 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) + then + // we restrict wildcard export from package as incremental compilation does not yet + // register a dependency on "all members of a package" - see https://github.com/sbt/zinc/issues/226 + report.error( + em"Implementation restriction: ${path.tpe.classSymbol} is not a valid prefix " + + "for a wildcard export, as it is a package.", path.srcPos) + /** Check that `tp` is a class type. * Also, if `traitReq` is true, check that `tp` is a trait. * Also, if `stablePrefixReq` is true and phase is not after RefChecks, diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 2f254847e378..855a9093ae5e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -945,7 +945,7 @@ class Namer { typer: Typer => val buf = new mutable.ListBuffer[tpd.MemberDef] val Export(expr, selectors) = exp val path = typedAheadExpr(expr, AnySelectionProto) - checkLegalImportPath(path) + checkLegalExportPath(path, selectors) lazy val wildcardBound = importBound(selectors, isGiven = false) lazy val givenBound = importBound(selectors, isGiven = true) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index df85a4f1c5a4..36825dbb6ec1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2235,29 +2235,26 @@ class Typer extends Namer def localDummy(cls: ClassSymbol, impl: untpd.Template)(using Context): Symbol = newLocalDummy(cls, impl.span) - inline def typedImportOrExport[T <: ImportOrExport[Untyped]](imp: T)( - inline mkTree: (Tree, List[untpd.ImportSelector]) => imp.ThisTree[Type])(using Context): imp.ThisTree[Type] = { - val expr1 = typedExpr(imp.expr, AnySelectionProto) - checkLegalImportPath(expr1) - val selectors1: List[untpd.ImportSelector] = imp.selectors.mapConserve { sel => + inline private def typedSelectors(selectors: List[untpd.ImportSelector])(using Context): List[untpd.ImportSelector] = + selectors.mapConserve { sel => if sel.bound.isEmpty then sel else cpy.ImportSelector(sel)( sel.imported, sel.renamed, untpd.TypedSplice(typedType(sel.bound))) .asInstanceOf[untpd.ImportSelector] } - mkTree(expr1, selectors1) - } def typedImport(imp: untpd.Import, sym: Symbol)(using Context): Import = { - typedImportOrExport(imp)((expr1, selectors1) => - assignType(cpy.Import(imp)(expr1, selectors1), sym) - ) + val expr1 = typedExpr(imp.expr, AnySelectionProto) + checkLegalImportPath(expr1) + val selectors1 = typedSelectors(imp.selectors) + assignType(cpy.Import(imp)(expr1, selectors1), sym) } def typedExport(exp: untpd.Export)(using Context): Export = { - typedImportOrExport(exp)((expr1, selectors1) => - assignType(cpy.Export(exp)(expr1, selectors1)) - ) + val expr1 = typedExpr(exp.expr, AnySelectionProto) + // already called `checkLegalExportPath` in Namer + val selectors1 = typedSelectors(exp.selectors) + assignType(cpy.Export(exp)(expr1, selectors1)) } def typedPackageDef(tree: untpd.PackageDef)(using Context): Tree = diff --git a/tests/neg/package-export/defaults.scala b/tests/neg/package-export/defaults.scala new file mode 100644 index 000000000000..ce27ba6d2b75 --- /dev/null +++ b/tests/neg/package-export/defaults.scala @@ -0,0 +1,3 @@ +package defaults + +given numerics.Ring[Int] = ??? diff --git a/tests/neg/package-export/enums.scala b/tests/neg/package-export/enums.scala new file mode 100644 index 000000000000..5b4febcc2c60 --- /dev/null +++ b/tests/neg/package-export/enums.scala @@ -0,0 +1,5 @@ +package enums + +def enumOrdinal[T](t: T): Int = t match + case t: reflect.Enum => t.ordinal + case t => -1 diff --git a/tests/neg/package-export/numerics.scala b/tests/neg/package-export/numerics.scala new file mode 100644 index 000000000000..f6145b7855b3 --- /dev/null +++ b/tests/neg/package-export/numerics.scala @@ -0,0 +1,3 @@ +package numerics + +trait Ring[T] diff --git a/tests/neg/package-export/package.scala b/tests/neg/package-export/package.scala new file mode 100644 index 000000000000..2fe5cd7df5ff --- /dev/null +++ b/tests/neg/package-export/package.scala @@ -0,0 +1,7 @@ +package mylib + +export numerics._ // error: package numerics is not a valid prefix for a wildcard export... + +export defaults.{given Ring[Int]} // error: package defaults is not a valid prefix for a wildcard export... + +export enums.enumOrdinal // ok, enums is a package but export is not wildcard From 2c6eeb2ccb328650161487df6132d8061a12bad7 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Thu, 3 Dec 2020 16:30:36 +0100 Subject: [PATCH 4/4] bump tasty --- tasty/src/dotty/tools/tasty/TastyFormat.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index 2be4fb931341..d9ec4fc49bc1 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -261,7 +261,7 @@ object TastyFormat { final val header: Array[Int] = Array(0x5C, 0xA1, 0xAB, 0x1F) val MajorVersion: Int = 26 - val MinorVersion: Int = 0 + val MinorVersion: Int = 1 final val ASTsSection = "ASTs" final val PositionsSection = "Positions"