Skip to content

Add Export as a Dual of Import #6169

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Apr 4, 2019
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1033,7 +1033,7 @@ object desugar {
case stat: TypeDef if stat.mods.is(Opaque) => stat.name
}
def needsObject(stat: Tree) = stat match {
case _: ValDef | _: PatDef | _: DefDef => true
case _: ValDef | _: PatDef | _: DefDef | _: Export => true
case stat: ModuleDef =>
stat.mods.is(ImplicitOrImplied) || opaqueNames.contains(stat.name.stripModuleClassSuffix.toTypeName)
case stat: TypeDef => !stat.isClassDef || stat.mods.is(ImplicitOrImplied)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1403,7 +1403,7 @@ object Trees {
this(x, rhs)
case tree @ Template(constr, parents, self, _) if tree.derived.isEmpty =>
this(this(this(this(x, constr), parents), self), tree.body)
case Import(importImplied, expr, selectors) =>
case Import(_, expr, _) =>
this(x, expr)
case PackageDef(pid, stats) =>
this(this(x, pid), stats)(localCtx)
Expand Down
11 changes: 11 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ 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(impliedOnly: Boolean, expr: Tree, selectors: List[Tree])(implicit @constructorOnly src: SourceFile) extends Tree

/** Short-lived usage in typer, does not need copy/transform/fold infrastructure */
case class DependentTypeTree(tp: List[Symbol] => Type)(implicit @constructorOnly src: SourceFile) extends Tree

@sharable object EmptyTypeIdent extends Ident(tpnme.EMPTY)(NoSource) with WithoutTypeOrPos[Untyped] {
Expand Down Expand Up @@ -532,6 +535,10 @@ 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)(impliedOnly: Boolean, expr: Tree, selectors: List[Tree])(implicit ctx: Context): Tree = tree match {
case tree: Export if (impliedOnly == tree.impliedOnly) && (expr eq tree.expr) && (selectors eq tree.selectors) => tree
case _ => finalize(tree, untpd.Export(impliedOnly, expr, selectors)(tree.source))
}
def TypedSplice(tree: Tree)(splice: tpd.Tree)(implicit ctx: Context): ProxyTree = tree match {
case tree: TypedSplice if splice `eq` tree.splice => tree
case _ => finalize(tree, untpd.TypedSplice(splice)(ctx))
Expand Down Expand Up @@ -584,6 +591,8 @@ 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(impliedOnly, expr, selectors) =>
cpy.Export(tree)(impliedOnly, transform(expr), selectors)
case TypedSplice(_) =>
tree
case _ =>
Expand Down Expand Up @@ -637,6 +646,8 @@ 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 TypedSplice(splice) =>
this(x, splice)
case _ =>
Expand Down
48 changes: 30 additions & 18 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2225,13 +2225,16 @@ object Parsers {
def finalizeDef(md: MemberDef, mods: Modifiers, start: Int): md.ThisTree[Untyped] =
md.withMods(mods).setComment(in.getDocComment(start))

type ImportConstr = (Boolean, Tree, List[Tree]) => Tree

/** Import ::= import [implied] [ImportExpr {`,' ImportExpr}
* Export ::= export [implied] [ImportExpr {`,' ImportExpr}
*/
def importClause(): List[Tree] = {
val offset = accept(IMPORT)
def importClause(leading: Token, mkTree: ImportConstr): List[Tree] = {
val offset = accept(leading)
val importImplied = in.token == IMPLIED
if (importImplied) in.nextToken()
commaSeparated(importExpr(importImplied)) match {
commaSeparated(importExpr(importImplied, mkTree)) match {
case t :: rest =>
// The first import should start at the start offset of the keyword.
val firstPos =
Expand All @@ -2244,23 +2247,28 @@ object Parsers {

/** ImportExpr ::= StableId `.' (id | `_' | ImportSelectors)
*/
def importExpr(importImplied: Boolean): () => Import = {
def importExpr(importImplied: Boolean, mkTree: ImportConstr): () => Tree = {

val handleImport: Tree => Tree = { tree: Tree =>
if (in.token == USCORE) Import(importImplied, tree, importSelector() :: Nil)
else if (in.token == LBRACE) Import(importImplied, tree, inBraces(importSelectors()))
if (in.token == USCORE) mkTree(importImplied, tree, importSelector() :: Nil)
else if (in.token == LBRACE) mkTree(importImplied, tree, inBraces(importSelectors()))
else tree
}

() => path(thisOK = false, handleImport) match {
case imp: Import =>
imp
case sel @ Select(qual, name) =>
val selector = atSpan(pointOffset(sel)) { Ident(name) }
cpy.Import(sel)(importImplied, qual, selector :: Nil)
case t =>
accept(DOT)
Import(importImplied, t, Ident(nme.WILDCARD) :: Nil)
def derived(impExp: Tree, qual: Tree, selectors: List[Tree]) =
mkTree(importImplied, qual, selectors).withSpan(impExp.span)

() => {
val p = path(thisOK = false, handleImport)
p match {
case _: Import | _: Export => p
case sel @ Select(qual, name) =>
val selector = atSpan(pointOffset(sel)) { Ident(name) }
mkTree(importImplied, qual, selector :: Nil).withSpan(sel.span)
case t =>
accept(DOT)
mkTree(importImplied, t, Ident(nme.WILDCARD) :: Nil)
}
}
}

Expand Down Expand Up @@ -2769,7 +2777,9 @@ object Parsers {
else stats += packaging(start)
}
else if (in.token == IMPORT)
stats ++= importClause()
stats ++= importClause(IMPORT, Import)
else if (in.token == EXPORT)
stats ++= importClause(EXPORT, Export.apply)
else if (in.token == AT || isDefIntro(modifierTokens))
stats +++= defOrDcl(in.offset, defAnnotsMods(modifierTokens))
else if (!isStatSep) {
Expand Down Expand Up @@ -2816,7 +2826,9 @@ object Parsers {
while (!isStatSeqEnd && !exitOnError) {
setLastStatOffset()
if (in.token == IMPORT)
stats ++= importClause()
stats ++= importClause(IMPORT, Import)
else if (in.token == EXPORT)
stats ++= importClause(EXPORT, Export.apply)
else if (isExprIntro)
stats += expr1()
else if (isDefIntro(modifierTokensOrCase))
Expand Down Expand Up @@ -2888,7 +2900,7 @@ object Parsers {
while (!isStatSeqEnd && in.token != CASE && !exitOnError) {
setLastStatOffset()
if (in.token == IMPORT)
stats ++= importClause()
stats ++= importClause(IMPORT, Import)
else if (in.token == GIVEN)
stats += implicitClosure(in.offset, Location.InBlock, modifiers(closureMods))
else if (isExprIntro)
Expand Down
5 changes: 3 additions & 2 deletions compiler/src/dotty/tools/dotc/parsing/Tokens.scala
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,8 @@ object Tokens extends TokensCommon {
final val ERASED = 63; enter(ERASED, "erased")
final val IMPLIED = 64; enter(IMPLIED, "implied")
final val GIVEN = 65; enter(GIVEN, "given")
final val MACRO = 66; enter(MACRO, "macro") // TODO: remove
final val EXPORT = 66; enter(EXPORT, "export")
final val MACRO = 67; enter(MACRO, "macro") // TODO: remove

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

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

final val canStartStatTokens: TokenSet = canStartExpressionTokens | mustStartStatTokens | BitSet(
AT, CASE)
Expand Down
6 changes: 6 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,12 @@ trait Checking {
ctx.error(ex"$cls cannot be instantiated since it${rstatus.msg}", pos)
}

/** Check that `path` is a legal prefix for an import or export clause */
def checkLegalImportPath(path: Tree)(implicit ctx: Context): Unit = {
checkStable(path.tpe, path.sourcePos)
if (!ctx.isAfterTyper) Checking.checkRealizable(path.tpe, path.posd)
}

/** 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,
Expand Down
116 changes: 116 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ class Namer { typer: Typer =>

val TypedAhead: Property.Key[tpd.Tree] = new Property.Key
val ExpandedTree: Property.Key[untpd.Tree] = new Property.Key
val ExportForwarders: Property.Key[List[tpd.MemberDef]] = new Property.Key
val SymOfTree: Property.Key[Symbol] = new Property.Key
val Deriver: Property.Key[typer.Deriver] = new Property.Key

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

def init(): Context = index(params)

/** Add forwarders as required by the export statements in this class */
private def processExports(implicit ctx: Context): Unit = {

/** A string indicating that no forwarders for this kind of symbol are emitted */
val SKIP = "(skip)"

/** The forwarders defined by export `exp`.
*/
def exportForwarders(exp: Export): List[tpd.MemberDef] = {
val buf = new mutable.ListBuffer[tpd.MemberDef]
val Export(_, expr, selectors) = exp
val path = typedAheadExpr(expr, AnySelectionProto)
checkLegalImportPath(path)

def whyNoForwarder(mbr: SingleDenotation): String = {
val sym = mbr.symbol
if (sym.is(ImplicitOrImplied) != exp.impliedOnly) s"is ${if (exp.impliedOnly) "not " else ""}implied"
else if (!sym.isAccessibleFrom(path.tpe)) "is not accessible"
else if (sym.isConstructor || sym.is(ModuleClass) || sym.is(Bridge)) SKIP
else if (cls.derivesFrom(sym.owner) &&
(sym.owner == cls || !sym.is(Deferred))) i"is already a member of $cls"
else ""
}

/** Add a forwarder with name `alias` or its type name equivalent to `mbr`,
* provided `mbr` is accessible and of the right implicit/non-implicit kind.
*/
def addForwarder(alias: TermName, mbr: SingleDenotation, span: Span): Unit = {
if (whyNoForwarder(mbr) == "") {

/** The info of a forwarder to type `ref` which has info `info`
*/
def fwdInfo(ref: Type, info: Type): Type = info match {
case _: ClassInfo =>
HKTypeLambda.fromParams(info.typeParams, ref)
case _: TypeBounds =>
TypeAlias(ref)
case info: HKTypeLambda =>
info.derivedLambdaType(info.paramNames, info.paramInfos,
fwdInfo(ref.appliedTo(info.paramRefs), info.resultType))
case info => // should happen only in error cases
info
}

val forwarder =
if (mbr.isType)
ctx.newSymbol(
cls, alias.toTypeName,
Final,
fwdInfo(path.tpe.select(mbr.symbol), mbr.info),
coord = span)
else {
val maybeStable = if (mbr.symbol.isStableMember) StableRealizable else EmptyFlags
ctx.newSymbol(
cls, alias,
Method | Final | maybeStable | mbr.symbol.flags & ImplicitOrImplied,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Synthetic?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I don't think we want to make them Synthetic .

mbr.info.ensureMethodic,
coord = span)
}
val forwarderDef =
if (forwarder.isType) tpd.TypeDef(forwarder.asType)
else {
import tpd._
val ref = path.select(mbr.symbol.asTerm)
tpd.polyDefDef(forwarder.asTerm, targs => prefss =>
ref.appliedToTypes(targs).appliedToArgss(prefss)
)
}
buf += forwarderDef.withSpan(span)
}
}

def addForwardersNamed(name: TermName, alias: TermName, span: Span): Unit = {
val size = buf.size
val mbrs = List(name, name.toTypeName).flatMap(path.tpe.member(_).alternatives)
mbrs.foreach(addForwarder(alias, _, span))
if (buf.size == size) {
val reason = mbrs.map(whyNoForwarder).dropWhile(_ == SKIP) match {
case Nil => ""
case why :: _ => i"\n$path.$name cannot be exported because it $why"
}
ctx.error(i"""no eligible member $name at $path$reason""", ctx.source.atSpan(span))
}
}

def addForwardersExcept(seen: List[TermName], span: Span): Unit =
for (mbr <- path.tpe.allMembers) {
val alias = mbr.name.toTermName
if (!seen.contains(alias)) addForwarder(alias, mbr, span)
}

def recur(seen: List[TermName], sels: List[untpd.Tree]): Unit = sels match {
case (sel @ Ident(nme.WILDCARD)) :: _ =>
addForwardersExcept(seen, sel.span)
case (sel @ Ident(name: TermName)) :: rest =>
addForwardersNamed(name, name, sel.span)
recur(name :: seen, rest)
case Thicket((sel @ Ident(fromName: TermName)) :: Ident(toName: TermName) :: Nil) :: rest =>
if (toName != nme.WILDCARD) addForwardersNamed(fromName, toName, sel.span)
recur(fromName :: seen, rest)
case _ =>
}

recur(Nil, selectors)
val forwarders = buf.toList
exp.pushAttachment(ExportForwarders, forwarders)
forwarders
}

val forwarderss =
for (exp @ Export(_, _, _) <- rest) yield exportForwarders(exp)
forwarderss.foreach(_.foreach(fwdr => fwdr.symbol.entered))
}

/** The type signature of a ClassDef with given symbol */
override def completeInCreationContext(denot: SymDenotation): Unit = {
val parents = impl.parents
Expand Down Expand Up @@ -1074,6 +1189,7 @@ class Namer { typer: Typer =>
cls.baseClasses.foreach(_.invalidateBaseTypeCache()) // we might have looked before and found nothing
cls.setNoInitsFlags(parentsKind(parents), bodyKind(rest))
if (cls.isNoInitsClass) cls.primaryConstructor.setFlag(StableRealizable)
processExports(localCtx)
}
}

Expand Down
7 changes: 5 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1780,8 +1780,7 @@ class Typer extends Namer

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

Expand Down Expand Up @@ -2205,6 +2204,10 @@ class Typer extends Namer
}
case Thicket(stats) :: rest =>
traverse(stats ++ rest)
case (stat: untpd.Export) :: rest =>
buf ++= stat.attachmentOrElse(ExportForwarders, Nil)
// no attachment can happen in case of cyclic references
traverse(rest)
case stat :: rest =>
val stat1 = typed(stat)(ctx.exprContext(stat, exprOwner))
checkStatementPurity(stat1)(stat, exprOwner)
Expand Down
5 changes: 4 additions & 1 deletion docs/docs/internals/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ Import ::= ‘import’ [‘implied’] ImportExpr {‘,’ ImportEx
ImportExpr ::= StableId ‘.’ (id | ‘_’ | ImportSelectors) Import(expr, sels)
ImportSelectors ::= ‘{’ {ImportSelector ‘,’} (ImportSelector | ‘_’) ‘}’
ImportSelector ::= id [‘=>’ id | ‘=>’ ‘_’] Ident(name), Pair(id, id)
Export ::= ‘export’ [‘implied’] ImportExpr {‘,’ ImportExpr}
```

### Declarations and Definitions
Expand Down Expand Up @@ -370,7 +371,8 @@ DefDef ::= DefSig [(‘:’ | ‘<:’) Type] ‘=’ Expr
TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef
| [‘case’] ‘object’ ObjectDef
| ‘enum’ EnumDef
| ‘witness’ WitnessDef
| ‘implied’ InstanceDef
| Export
ClassDef ::= id ClassConstr [Template] ClassDef(mods, name, tparams, templ)
ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses with DefDef(_, <init>, Nil, vparamss, EmptyTree, EmptyTree) as first stat
ConstrMods ::= {Annotation} [AccessModifier]
Expand Down Expand Up @@ -406,6 +408,7 @@ EnumCase ::= ‘case’ (id ClassConstr [‘extends’ ConstrApps]] |

TopStatSeq ::= TopStat {semi TopStat}
TopStat ::= Import
| Export
| {Annotation [nl]} {Modifier} Def
| Packaging
| PackageObject
Expand Down
Loading