Skip to content
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