Skip to content

Commit 5b0a7ce

Browse files
authored
Merge pull request #6169 from dotty-staging/add-export-2
Add Export as a Dual of Import
2 parents d00a7ba + 1731ba7 commit 5b0a7ce

File tree

16 files changed

+488
-25
lines changed

16 files changed

+488
-25
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1033,7 +1033,7 @@ object desugar {
10331033
case stat: TypeDef if stat.mods.is(Opaque) => stat.name
10341034
}
10351035
def needsObject(stat: Tree) = stat match {
1036-
case _: ValDef | _: PatDef | _: DefDef => true
1036+
case _: ValDef | _: PatDef | _: DefDef | _: Export => true
10371037
case stat: ModuleDef =>
10381038
stat.mods.is(ImplicitOrImplied) || opaqueNames.contains(stat.name.stripModuleClassSuffix.toTypeName)
10391039
case stat: TypeDef => !stat.isClassDef || stat.mods.is(ImplicitOrImplied)

compiler/src/dotty/tools/dotc/ast/Trees.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1403,7 +1403,7 @@ object Trees {
14031403
this(x, rhs)
14041404
case tree @ Template(constr, parents, self, _) if tree.derived.isEmpty =>
14051405
this(this(this(this(x, constr), parents), self), tree.body)
1406-
case Import(importImplied, expr, selectors) =>
1406+
case Import(_, expr, _) =>
14071407
this(x, expr)
14081408
case PackageDef(pid, stats) =>
14091409
this(this(x, pid), stats)(localCtx)

compiler/src/dotty/tools/dotc/ast/untpd.scala

+11
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
103103
case class GenAlias(pat: Tree, expr: Tree)(implicit @constructorOnly src: SourceFile) extends Tree
104104
case class ContextBounds(bounds: TypeBoundsTree, cxBounds: List[Tree])(implicit @constructorOnly src: SourceFile) extends TypTree
105105
case class PatDef(mods: Modifiers, pats: List[Tree], tpt: Tree, rhs: Tree)(implicit @constructorOnly src: SourceFile) extends DefTree
106+
case class Export(impliedOnly: Boolean, expr: Tree, selectors: List[Tree])(implicit @constructorOnly src: SourceFile) extends Tree
107+
108+
/** Short-lived usage in typer, does not need copy/transform/fold infrastructure */
106109
case class DependentTypeTree(tp: List[Symbol] => Type)(implicit @constructorOnly src: SourceFile) extends Tree
107110

108111
@sharable object EmptyTypeIdent extends Ident(tpnme.EMPTY)(NoSource) with WithoutTypeOrPos[Untyped] {
@@ -532,6 +535,10 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
532535
case tree: PatDef if (mods eq tree.mods) && (pats eq tree.pats) && (tpt eq tree.tpt) && (rhs eq tree.rhs) => tree
533536
case _ => finalize(tree, untpd.PatDef(mods, pats, tpt, rhs)(tree.source))
534537
}
538+
def Export(tree: Tree)(impliedOnly: Boolean, expr: Tree, selectors: List[Tree])(implicit ctx: Context): Tree = tree match {
539+
case tree: Export if (impliedOnly == tree.impliedOnly) && (expr eq tree.expr) && (selectors eq tree.selectors) => tree
540+
case _ => finalize(tree, untpd.Export(impliedOnly, expr, selectors)(tree.source))
541+
}
535542
def TypedSplice(tree: Tree)(splice: tpd.Tree)(implicit ctx: Context): ProxyTree = tree match {
536543
case tree: TypedSplice if splice `eq` tree.splice => tree
537544
case _ => finalize(tree, untpd.TypedSplice(splice)(ctx))
@@ -584,6 +591,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
584591
cpy.ContextBounds(tree)(transformSub(bounds), transform(cxBounds))
585592
case PatDef(mods, pats, tpt, rhs) =>
586593
cpy.PatDef(tree)(mods, transform(pats), transform(tpt), transform(rhs))
594+
case Export(impliedOnly, expr, selectors) =>
595+
cpy.Export(tree)(impliedOnly, transform(expr), selectors)
587596
case TypedSplice(_) =>
588597
tree
589598
case _ =>
@@ -637,6 +646,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
637646
this(this(x, bounds), cxBounds)
638647
case PatDef(mods, pats, tpt, rhs) =>
639648
this(this(this(x, pats), tpt), rhs)
649+
case Export(_, expr, _) =>
650+
this(x, expr)
640651
case TypedSplice(splice) =>
641652
this(x, splice)
642653
case _ =>

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

+30-18
Original file line numberDiff line numberDiff line change
@@ -2225,13 +2225,16 @@ object Parsers {
22252225
def finalizeDef(md: MemberDef, mods: Modifiers, start: Int): md.ThisTree[Untyped] =
22262226
md.withMods(mods).setComment(in.getDocComment(start))
22272227

2228+
type ImportConstr = (Boolean, Tree, List[Tree]) => Tree
2229+
22282230
/** Import ::= import [implied] [ImportExpr {`,' ImportExpr}
2231+
* Export ::= export [implied] [ImportExpr {`,' ImportExpr}
22292232
*/
2230-
def importClause(): List[Tree] = {
2231-
val offset = accept(IMPORT)
2233+
def importClause(leading: Token, mkTree: ImportConstr): List[Tree] = {
2234+
val offset = accept(leading)
22322235
val importImplied = in.token == IMPLIED
22332236
if (importImplied) in.nextToken()
2234-
commaSeparated(importExpr(importImplied)) match {
2237+
commaSeparated(importExpr(importImplied, mkTree)) match {
22352238
case t :: rest =>
22362239
// The first import should start at the start offset of the keyword.
22372240
val firstPos =
@@ -2244,23 +2247,28 @@ object Parsers {
22442247

22452248
/** ImportExpr ::= StableId `.' (id | `_' | ImportSelectors)
22462249
*/
2247-
def importExpr(importImplied: Boolean): () => Import = {
2250+
def importExpr(importImplied: Boolean, mkTree: ImportConstr): () => Tree = {
22482251

22492252
val handleImport: Tree => Tree = { tree: Tree =>
2250-
if (in.token == USCORE) Import(importImplied, tree, importSelector() :: Nil)
2251-
else if (in.token == LBRACE) Import(importImplied, tree, inBraces(importSelectors()))
2253+
if (in.token == USCORE) mkTree(importImplied, tree, importSelector() :: Nil)
2254+
else if (in.token == LBRACE) mkTree(importImplied, tree, inBraces(importSelectors()))
22522255
else tree
22532256
}
22542257

2255-
() => path(thisOK = false, handleImport) match {
2256-
case imp: Import =>
2257-
imp
2258-
case sel @ Select(qual, name) =>
2259-
val selector = atSpan(pointOffset(sel)) { Ident(name) }
2260-
cpy.Import(sel)(importImplied, qual, selector :: Nil)
2261-
case t =>
2262-
accept(DOT)
2263-
Import(importImplied, t, Ident(nme.WILDCARD) :: Nil)
2258+
def derived(impExp: Tree, qual: Tree, selectors: List[Tree]) =
2259+
mkTree(importImplied, qual, selectors).withSpan(impExp.span)
2260+
2261+
() => {
2262+
val p = path(thisOK = false, handleImport)
2263+
p match {
2264+
case _: Import | _: Export => p
2265+
case sel @ Select(qual, name) =>
2266+
val selector = atSpan(pointOffset(sel)) { Ident(name) }
2267+
mkTree(importImplied, qual, selector :: Nil).withSpan(sel.span)
2268+
case t =>
2269+
accept(DOT)
2270+
mkTree(importImplied, t, Ident(nme.WILDCARD) :: Nil)
2271+
}
22642272
}
22652273
}
22662274

@@ -2769,7 +2777,9 @@ object Parsers {
27692777
else stats += packaging(start)
27702778
}
27712779
else if (in.token == IMPORT)
2772-
stats ++= importClause()
2780+
stats ++= importClause(IMPORT, Import)
2781+
else if (in.token == EXPORT)
2782+
stats ++= importClause(EXPORT, Export.apply)
27732783
else if (in.token == AT || isDefIntro(modifierTokens))
27742784
stats +++= defOrDcl(in.offset, defAnnotsMods(modifierTokens))
27752785
else if (!isStatSep) {
@@ -2816,7 +2826,9 @@ object Parsers {
28162826
while (!isStatSeqEnd && !exitOnError) {
28172827
setLastStatOffset()
28182828
if (in.token == IMPORT)
2819-
stats ++= importClause()
2829+
stats ++= importClause(IMPORT, Import)
2830+
else if (in.token == EXPORT)
2831+
stats ++= importClause(EXPORT, Export.apply)
28202832
else if (isExprIntro)
28212833
stats += expr1()
28222834
else if (isDefIntro(modifierTokensOrCase))
@@ -2888,7 +2900,7 @@ object Parsers {
28882900
while (!isStatSeqEnd && in.token != CASE && !exitOnError) {
28892901
setLastStatOffset()
28902902
if (in.token == IMPORT)
2891-
stats ++= importClause()
2903+
stats ++= importClause(IMPORT, Import)
28922904
else if (in.token == GIVEN)
28932905
stats += implicitClosure(in.offset, Location.InBlock, modifiers(closureMods))
28942906
else if (isExprIntro)

compiler/src/dotty/tools/dotc/parsing/Tokens.scala

+3-2
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,8 @@ object Tokens extends TokensCommon {
180180
final val ERASED = 63; enter(ERASED, "erased")
181181
final val IMPLIED = 64; enter(IMPLIED, "implied")
182182
final val GIVEN = 65; enter(GIVEN, "given")
183-
final val MACRO = 66; enter(MACRO, "macro") // TODO: remove
183+
final val EXPORT = 66; enter(EXPORT, "export")
184+
final val MACRO = 67; enter(MACRO, "macro") // TODO: remove
184185

185186
/** special symbols */
186187
final val NEWLINE = 78; enter(NEWLINE, "end of statement", "new line")
@@ -240,7 +241,7 @@ object Tokens extends TokensCommon {
240241
final val modifierFollowers = modifierTokens | defIntroTokens
241242

242243
/** Is token only legal as start of statement (eof also included)? */
243-
final val mustStartStatTokens: TokenSet = defIntroTokens | modifierTokens | BitSet(IMPORT, PACKAGE)
244+
final val mustStartStatTokens: TokenSet = defIntroTokens | modifierTokens | BitSet(IMPORT, EXPORT, PACKAGE)
244245

245246
final val canStartStatTokens: TokenSet = canStartExpressionTokens | mustStartStatTokens | BitSet(
246247
AT, CASE)

compiler/src/dotty/tools/dotc/typer/Checking.scala

+6
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,12 @@ trait Checking {
594594
ctx.error(ex"$cls cannot be instantiated since it${rstatus.msg}", pos)
595595
}
596596

597+
/** Check that `path` is a legal prefix for an import or export clause */
598+
def checkLegalImportPath(path: Tree)(implicit ctx: Context): Unit = {
599+
checkStable(path.tpe, path.sourcePos)
600+
if (!ctx.isAfterTyper) Checking.checkRealizable(path.tpe, path.posd)
601+
}
602+
597603
/** Check that `tp` is a class type.
598604
* Also, if `traitReq` is true, check that `tp` is a trait.
599605
* Also, if `stablePrefixReq` is true and phase is not after RefChecks,

compiler/src/dotty/tools/dotc/typer/Namer.scala

+116
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ class Namer { typer: Typer =>
195195

196196
val TypedAhead: Property.Key[tpd.Tree] = new Property.Key
197197
val ExpandedTree: Property.Key[untpd.Tree] = new Property.Key
198+
val ExportForwarders: Property.Key[List[tpd.MemberDef]] = new Property.Key
198199
val SymOfTree: Property.Key[Symbol] = new Property.Key
199200
val Deriver: Property.Key[typer.Deriver] = new Property.Key
200201

@@ -932,6 +933,120 @@ class Namer { typer: Typer =>
932933

933934
def init(): Context = index(params)
934935

936+
/** Add forwarders as required by the export statements in this class */
937+
private def processExports(implicit ctx: Context): Unit = {
938+
939+
/** A string indicating that no forwarders for this kind of symbol are emitted */
940+
val SKIP = "(skip)"
941+
942+
/** The forwarders defined by export `exp`.
943+
*/
944+
def exportForwarders(exp: Export): List[tpd.MemberDef] = {
945+
val buf = new mutable.ListBuffer[tpd.MemberDef]
946+
val Export(_, expr, selectors) = exp
947+
val path = typedAheadExpr(expr, AnySelectionProto)
948+
checkLegalImportPath(path)
949+
950+
def whyNoForwarder(mbr: SingleDenotation): String = {
951+
val sym = mbr.symbol
952+
if (sym.is(ImplicitOrImplied) != exp.impliedOnly) s"is ${if (exp.impliedOnly) "not " else ""}implied"
953+
else if (!sym.isAccessibleFrom(path.tpe)) "is not accessible"
954+
else if (sym.isConstructor || sym.is(ModuleClass) || sym.is(Bridge)) SKIP
955+
else if (cls.derivesFrom(sym.owner) &&
956+
(sym.owner == cls || !sym.is(Deferred))) i"is already a member of $cls"
957+
else ""
958+
}
959+
960+
/** Add a forwarder with name `alias` or its type name equivalent to `mbr`,
961+
* provided `mbr` is accessible and of the right implicit/non-implicit kind.
962+
*/
963+
def addForwarder(alias: TermName, mbr: SingleDenotation, span: Span): Unit = {
964+
if (whyNoForwarder(mbr) == "") {
965+
966+
/** The info of a forwarder to type `ref` which has info `info`
967+
*/
968+
def fwdInfo(ref: Type, info: Type): Type = info match {
969+
case _: ClassInfo =>
970+
HKTypeLambda.fromParams(info.typeParams, ref)
971+
case _: TypeBounds =>
972+
TypeAlias(ref)
973+
case info: HKTypeLambda =>
974+
info.derivedLambdaType(info.paramNames, info.paramInfos,
975+
fwdInfo(ref.appliedTo(info.paramRefs), info.resultType))
976+
case info => // should happen only in error cases
977+
info
978+
}
979+
980+
val forwarder =
981+
if (mbr.isType)
982+
ctx.newSymbol(
983+
cls, alias.toTypeName,
984+
Final,
985+
fwdInfo(path.tpe.select(mbr.symbol), mbr.info),
986+
coord = span)
987+
else {
988+
val maybeStable = if (mbr.symbol.isStableMember) StableRealizable else EmptyFlags
989+
ctx.newSymbol(
990+
cls, alias,
991+
Method | Final | maybeStable | mbr.symbol.flags & ImplicitOrImplied,
992+
mbr.info.ensureMethodic,
993+
coord = span)
994+
}
995+
val forwarderDef =
996+
if (forwarder.isType) tpd.TypeDef(forwarder.asType)
997+
else {
998+
import tpd._
999+
val ref = path.select(mbr.symbol.asTerm)
1000+
tpd.polyDefDef(forwarder.asTerm, targs => prefss =>
1001+
ref.appliedToTypes(targs).appliedToArgss(prefss)
1002+
)
1003+
}
1004+
buf += forwarderDef.withSpan(span)
1005+
}
1006+
}
1007+
1008+
def addForwardersNamed(name: TermName, alias: TermName, span: Span): Unit = {
1009+
val size = buf.size
1010+
val mbrs = List(name, name.toTypeName).flatMap(path.tpe.member(_).alternatives)
1011+
mbrs.foreach(addForwarder(alias, _, span))
1012+
if (buf.size == size) {
1013+
val reason = mbrs.map(whyNoForwarder).dropWhile(_ == SKIP) match {
1014+
case Nil => ""
1015+
case why :: _ => i"\n$path.$name cannot be exported because it $why"
1016+
}
1017+
ctx.error(i"""no eligible member $name at $path$reason""", ctx.source.atSpan(span))
1018+
}
1019+
}
1020+
1021+
def addForwardersExcept(seen: List[TermName], span: Span): Unit =
1022+
for (mbr <- path.tpe.allMembers) {
1023+
val alias = mbr.name.toTermName
1024+
if (!seen.contains(alias)) addForwarder(alias, mbr, span)
1025+
}
1026+
1027+
def recur(seen: List[TermName], sels: List[untpd.Tree]): Unit = sels match {
1028+
case (sel @ Ident(nme.WILDCARD)) :: _ =>
1029+
addForwardersExcept(seen, sel.span)
1030+
case (sel @ Ident(name: TermName)) :: rest =>
1031+
addForwardersNamed(name, name, sel.span)
1032+
recur(name :: seen, rest)
1033+
case Thicket((sel @ Ident(fromName: TermName)) :: Ident(toName: TermName) :: Nil) :: rest =>
1034+
if (toName != nme.WILDCARD) addForwardersNamed(fromName, toName, sel.span)
1035+
recur(fromName :: seen, rest)
1036+
case _ =>
1037+
}
1038+
1039+
recur(Nil, selectors)
1040+
val forwarders = buf.toList
1041+
exp.pushAttachment(ExportForwarders, forwarders)
1042+
forwarders
1043+
}
1044+
1045+
val forwarderss =
1046+
for (exp @ Export(_, _, _) <- rest) yield exportForwarders(exp)
1047+
forwarderss.foreach(_.foreach(fwdr => fwdr.symbol.entered))
1048+
}
1049+
9351050
/** The type signature of a ClassDef with given symbol */
9361051
override def completeInCreationContext(denot: SymDenotation): Unit = {
9371052
val parents = impl.parents
@@ -1074,6 +1189,7 @@ class Namer { typer: Typer =>
10741189
cls.baseClasses.foreach(_.invalidateBaseTypeCache()) // we might have looked before and found nothing
10751190
cls.setNoInitsFlags(parentsKind(parents), bodyKind(rest))
10761191
if (cls.isNoInitsClass) cls.primaryConstructor.setFlag(StableRealizable)
1192+
processExports(localCtx)
10771193
}
10781194
}
10791195

compiler/src/dotty/tools/dotc/typer/Typer.scala

+5-2
Original file line numberDiff line numberDiff line change
@@ -1780,8 +1780,7 @@ class Typer extends Namer
17801780

17811781
def typedImport(imp: untpd.Import, sym: Symbol)(implicit ctx: Context): Import = track("typedImport") {
17821782
val expr1 = typedExpr(imp.expr, AnySelectionProto)
1783-
checkStable(expr1.tpe, imp.expr.sourcePos)
1784-
if (!ctx.isAfterTyper) checkRealizable(expr1.tpe, imp.expr.posd)
1783+
checkLegalImportPath(expr1)
17851784
assignType(cpy.Import(imp)(imp.importImplied, expr1, imp.selectors), sym)
17861785
}
17871786

@@ -2205,6 +2204,10 @@ class Typer extends Namer
22052204
}
22062205
case Thicket(stats) :: rest =>
22072206
traverse(stats ++ rest)
2207+
case (stat: untpd.Export) :: rest =>
2208+
buf ++= stat.attachmentOrElse(ExportForwarders, Nil)
2209+
// no attachment can happen in case of cyclic references
2210+
traverse(rest)
22082211
case stat :: rest =>
22092212
val stat1 = typed(stat)(ctx.exprContext(stat, exprOwner))
22102213
checkStatementPurity(stat1)(stat, exprOwner)

docs/docs/internals/syntax.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ Import ::= ‘import’ [‘implied’] ImportExpr {‘,’ ImportEx
334334
ImportExpr ::= StableId ‘.’ (id | ‘_’ | ImportSelectors) Import(expr, sels)
335335
ImportSelectors ::= ‘{’ {ImportSelector ‘,’} (ImportSelector | ‘_’) ‘}’
336336
ImportSelector ::= id [‘=>’ id | ‘=>’ ‘_’] Ident(name), Pair(id, id)
337+
Export ::= ‘export’ [‘implied’] ImportExpr {‘,’ ImportExpr}
337338
```
338339

339340
### Declarations and Definitions
@@ -370,7 +371,8 @@ DefDef ::= DefSig [(‘:’ | ‘<:’) Type] ‘=’ Expr
370371
TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef
371372
| [‘case’] ‘object’ ObjectDef
372373
| ‘enum’ EnumDef
373-
| ‘witness’ WitnessDef
374+
| ‘implied’ InstanceDef
375+
| Export
374376
ClassDef ::= id ClassConstr [Template] ClassDef(mods, name, tparams, templ)
375377
ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses with DefDef(_, <init>, Nil, vparamss, EmptyTree, EmptyTree) as first stat
376378
ConstrMods ::= {Annotation} [AccessModifier]
@@ -406,6 +408,7 @@ EnumCase ::= ‘case’ (id ClassConstr [‘extends’ ConstrApps]] |
406408
407409
TopStatSeq ::= TopStat {semi TopStat}
408410
TopStat ::= Import
411+
| Export
409412
| {Annotation [nl]} {Modifier} Def
410413
| Packaging
411414
| PackageObject

0 commit comments

Comments
 (0)