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/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/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/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index b58e05e1129d..4d3cb79ea95e 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(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 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..36825dbb6ec1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2235,18 +2235,28 @@ 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 = { - 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] } + + def typedImport(imp: untpd.Import, sym: Symbol)(using Context): Import = { + 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 = { + 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 = val pid1 = withMode(Mode.InPackageClauseName)(typedExpr(tree.pid, AnySelectionProto)) val pkg = pid1.symbol @@ -2481,6 +2491,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 +2654,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..d9ec4fc49bc1 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)? @@ -260,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" @@ -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/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 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 ---------------------------------