Skip to content

Commit e012734

Browse files
authored
Merge pull request #12134 from dotty-staging/fix-12112
Allow export paths to see imports
2 parents e2ea418 + c16e41e commit e012734

File tree

3 files changed

+166
-148
lines changed

3 files changed

+166
-148
lines changed

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

Lines changed: 155 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -972,162 +972,171 @@ class Namer { typer: Typer =>
972972

973973
def init(): Context = index(params)
974974

975-
/** Add forwarders as required by the export statements in this class */
976-
private def processExports(using Context): Unit = {
975+
/** The forwarders defined by export `exp` */
976+
private def exportForwarders(exp: Export)(using Context): List[tpd.MemberDef] =
977+
val SKIP = "(skip)" // A string indicating that no forwarders for this kind of symbol are emitted
978+
val buf = new mutable.ListBuffer[tpd.MemberDef]
979+
val Export(expr, selectors) = exp
980+
if expr.isEmpty then
981+
report.error(em"Export selector must have prefix and `.`", exp.srcPos)
982+
return Nil
983+
984+
val path = typedAheadExpr(expr, AnySelectionProto)
985+
checkLegalExportPath(path, selectors)
986+
lazy val wildcardBound = importBound(selectors, isGiven = false)
987+
lazy val givenBound = importBound(selectors, isGiven = true)
988+
989+
def whyNoForwarder(mbr: SingleDenotation): String = {
990+
val sym = mbr.symbol
991+
if (!sym.isAccessibleFrom(path.tpe)) "is not accessible"
992+
else if (sym.isConstructor || sym.is(ModuleClass) || sym.is(Bridge) || sym.is(ConstructorProxy)) SKIP
993+
else if (cls.derivesFrom(sym.owner) &&
994+
(sym.owner == cls || !sym.is(Deferred))) i"is already a member of $cls"
995+
else if (sym.is(Override))
996+
sym.allOverriddenSymbols.find(
997+
other => cls.derivesFrom(other.owner) && !other.is(Deferred)) match {
998+
case Some(other) => i"overrides ${other.showLocated}, which is already a member of $cls"
999+
case None => ""
1000+
}
1001+
else ""
1002+
}
9771003

978-
/** A string indicating that no forwarders for this kind of symbol are emitted */
979-
val SKIP = "(skip)"
1004+
/** Add a forwarder with name `alias` or its type name equivalent to `mbr`,
1005+
* provided `mbr` is accessible and of the right implicit/non-implicit kind.
1006+
*/
1007+
def addForwarder(alias: TermName, mbr: SingleDenotation, span: Span): Unit =
1008+
1009+
def adaptForwarderParams(acc: List[List[tpd.Tree]], tp: Type, prefss: List[List[tpd.Tree]])
1010+
: List[List[tpd.Tree]] = tp match
1011+
case mt: MethodType
1012+
if mt.paramInfos.nonEmpty && mt.paramInfos.last.isRepeatedParam =>
1013+
// Note: in this branch we use the assumptions
1014+
// that `prefss.head` corresponds to `mt.paramInfos` and
1015+
// that `prefss.tail` corresponds to `mt.resType`
1016+
val init :+ vararg = prefss.head
1017+
val prefs = init :+ ctx.typeAssigner.seqToRepeated(vararg)
1018+
adaptForwarderParams(prefs :: acc, mt.resType, prefss.tail)
1019+
case mt: MethodOrPoly =>
1020+
adaptForwarderParams(prefss.head :: acc, mt.resultType, prefss.tail)
1021+
case _ =>
1022+
acc.reverse ::: prefss
9801023

981-
/** The forwarders defined by export `exp`.
982-
*/
983-
def exportForwarders(exp: Export): List[tpd.MemberDef] = {
984-
val buf = new mutable.ListBuffer[tpd.MemberDef]
985-
val Export(expr, selectors) = exp
986-
if expr.isEmpty then
987-
report.error(em"Export selector must have prefix and `.`", exp.srcPos)
988-
return Nil
989-
990-
val path = typedAheadExpr(expr, AnySelectionProto)
991-
checkLegalExportPath(path, selectors)
992-
lazy val wildcardBound = importBound(selectors, isGiven = false)
993-
lazy val givenBound = importBound(selectors, isGiven = true)
994-
995-
def whyNoForwarder(mbr: SingleDenotation): String = {
1024+
if whyNoForwarder(mbr) == "" then
9961025
val sym = mbr.symbol
997-
if (!sym.isAccessibleFrom(path.tpe)) "is not accessible"
998-
else if (sym.isConstructor || sym.is(ModuleClass) || sym.is(Bridge) || sym.is(ConstructorProxy)) SKIP
999-
else if (cls.derivesFrom(sym.owner) &&
1000-
(sym.owner == cls || !sym.is(Deferred))) i"is already a member of $cls"
1001-
else if (sym.is(Override))
1002-
sym.allOverriddenSymbols.find(
1003-
other => cls.derivesFrom(other.owner) && !other.is(Deferred)) match {
1004-
case Some(other) => i"overrides ${other.showLocated}, which is already a member of $cls"
1005-
case None => ""
1026+
val forwarder =
1027+
if mbr.isType then
1028+
val forwarderName = checkNoConflict(alias.toTypeName, isPrivate = false, span)
1029+
var target = path.tpe.select(sym)
1030+
if target.typeParams.nonEmpty then
1031+
target = target.EtaExpand(target.typeParams)
1032+
newSymbol(
1033+
cls, forwarderName,
1034+
Exported | Final,
1035+
TypeAlias(target),
1036+
coord = span)
1037+
// Note: This will always create unparameterzied aliases. So even if the original type is
1038+
// a parameterized class, say `C[X]` the alias will read `type C = d.C`. We currently do
1039+
// allow such type aliases. If we forbid them at some point (requiring the referred type to be
1040+
// fully applied), we'd have to change the scheme here as well.
1041+
else {
1042+
def refersToPrivate(tp: Type): Boolean = tp match
1043+
case tp: TermRef => tp.termSymbol.is(Private) || refersToPrivate(tp.prefix)
1044+
case _ => false
1045+
val (maybeStable, mbrInfo) =
1046+
if sym.isStableMember && sym.isPublic && !refersToPrivate(path.tpe) then
1047+
(StableRealizable, ExprType(path.tpe.select(sym)))
1048+
else
1049+
(EmptyFlags, mbr.info.ensureMethodic)
1050+
var mbrFlags = Exported | Method | Final | maybeStable | sym.flags & RetainedExportFlags
1051+
if sym.is(ExtensionMethod) then mbrFlags |= ExtensionMethod
1052+
val forwarderName = checkNoConflict(alias, isPrivate = false, span)
1053+
newSymbol(cls, forwarderName, mbrFlags, mbrInfo, coord = span)
10061054
}
1007-
else ""
1008-
}
1009-
1010-
/** Add a forwarder with name `alias` or its type name equivalent to `mbr`,
1011-
* provided `mbr` is accessible and of the right implicit/non-implicit kind.
1012-
*/
1013-
def addForwarder(alias: TermName, mbr: SingleDenotation, span: Span): Unit =
1014-
1015-
def adaptForwarderParams(acc: List[List[tpd.Tree]], tp: Type, prefss: List[List[tpd.Tree]])
1016-
: List[List[tpd.Tree]] = tp match
1017-
case mt: MethodType
1018-
if mt.paramInfos.nonEmpty && mt.paramInfos.last.isRepeatedParam =>
1019-
// Note: in this branch we use the assumptions
1020-
// that `prefss.head` corresponds to `mt.paramInfos` and
1021-
// that `prefss.tail` corresponds to `mt.resType`
1022-
val init :+ vararg = prefss.head
1023-
val prefs = init :+ ctx.typeAssigner.seqToRepeated(vararg)
1024-
adaptForwarderParams(prefs :: acc, mt.resType, prefss.tail)
1025-
case mt: MethodOrPoly =>
1026-
adaptForwarderParams(prefss.head :: acc, mt.resultType, prefss.tail)
1027-
case _ =>
1028-
acc.reverse ::: prefss
1029-
1030-
if whyNoForwarder(mbr) == "" then
1031-
val sym = mbr.symbol
1032-
val forwarder =
1033-
if mbr.isType then
1034-
val forwarderName = checkNoConflict(alias.toTypeName, isPrivate = false, span)
1035-
var target = path.tpe.select(sym)
1036-
if target.typeParams.nonEmpty then
1037-
target = target.EtaExpand(target.typeParams)
1038-
newSymbol(
1039-
cls, forwarderName,
1040-
Exported | Final,
1041-
TypeAlias(target),
1042-
coord = span)
1043-
// Note: This will always create unparameterzied aliases. So even if the original type is
1044-
// a parameterized class, say `C[X]` the alias will read `type C = d.C`. We currently do
1045-
// allow such type aliases. If we forbid them at some point (requiring the referred type to be
1046-
// fully applied), we'd have to change the scheme here as well.
1047-
else {
1048-
def refersToPrivate(tp: Type): Boolean = tp match
1049-
case tp: TermRef => tp.termSymbol.is(Private) || refersToPrivate(tp.prefix)
1050-
case _ => false
1051-
val (maybeStable, mbrInfo) =
1052-
if sym.isStableMember && sym.isPublic && !refersToPrivate(path.tpe) then
1053-
(StableRealizable, ExprType(path.tpe.select(sym)))
1054-
else
1055-
(EmptyFlags, mbr.info.ensureMethodic)
1056-
var mbrFlags = Exported | Method | Final | maybeStable | sym.flags & RetainedExportFlags
1057-
if sym.is(ExtensionMethod) then mbrFlags |= ExtensionMethod
1058-
val forwarderName = checkNoConflict(alias, isPrivate = false, span)
1059-
newSymbol(cls, forwarderName, mbrFlags, mbrInfo, coord = span)
1060-
}
1061-
forwarder.info = avoidPrivateLeaks(forwarder)
1062-
forwarder.addAnnotations(sym.annotations)
1063-
val forwarderDef =
1064-
if (forwarder.isType) tpd.TypeDef(forwarder.asType)
1065-
else {
1066-
import tpd._
1067-
val ref = path.select(sym.asTerm)
1068-
val ddef = tpd.DefDef(forwarder.asTerm, prefss =>
1069-
ref.appliedToArgss(adaptForwarderParams(Nil, sym.info, prefss))
1070-
)
1071-
if forwarder.isInlineMethod then
1072-
PrepareInlineable.registerInlineInfo(forwarder, ddef.rhs)
1073-
ddef
1074-
}
1075-
1076-
buf += forwarderDef.withSpan(span)
1077-
end addForwarder
1078-
1079-
def addForwardersNamed(name: TermName, alias: TermName, span: Span): Unit = {
1080-
val size = buf.size
1081-
val mbrs = List(name, name.toTypeName).flatMap(path.tpe.member(_).alternatives)
1082-
mbrs.foreach(addForwarder(alias, _, span))
1083-
if (buf.size == size) {
1084-
val reason = mbrs.map(whyNoForwarder).dropWhile(_ == SKIP) match {
1085-
case Nil => ""
1086-
case why :: _ => i"\n$path.$name cannot be exported because it $why"
1055+
forwarder.info = avoidPrivateLeaks(forwarder)
1056+
forwarder.addAnnotations(sym.annotations)
1057+
val forwarderDef =
1058+
if (forwarder.isType) tpd.TypeDef(forwarder.asType)
1059+
else {
1060+
import tpd._
1061+
val ref = path.select(sym.asTerm)
1062+
val ddef = tpd.DefDef(forwarder.asTerm, prefss =>
1063+
ref.appliedToArgss(adaptForwarderParams(Nil, sym.info, prefss))
1064+
)
1065+
if forwarder.isInlineMethod then
1066+
PrepareInlineable.registerInlineInfo(forwarder, ddef.rhs)
1067+
ddef
10871068
}
1088-
report.error(i"""no eligible member $name at $path$reason""", ctx.source.atSpan(span))
1069+
1070+
buf += forwarderDef.withSpan(span)
1071+
end addForwarder
1072+
1073+
def addForwardersNamed(name: TermName, alias: TermName, span: Span): Unit = {
1074+
val size = buf.size
1075+
val mbrs = List(name, name.toTypeName).flatMap(path.tpe.member(_).alternatives)
1076+
mbrs.foreach(addForwarder(alias, _, span))
1077+
if (buf.size == size) {
1078+
val reason = mbrs.map(whyNoForwarder).dropWhile(_ == SKIP) match {
1079+
case Nil => ""
1080+
case why :: _ => i"\n$path.$name cannot be exported because it $why"
10891081
}
1082+
report.error(i"""no eligible member $name at $path$reason""", ctx.source.atSpan(span))
10901083
}
1084+
}
10911085

1092-
def addWildcardForwardersNamed(name: TermName, span: Span): Unit =
1093-
List(name, name.toTypeName)
1094-
.flatMap(path.tpe.memberBasedOnFlags(_, excluded = Private|Given|ConstructorProxy).alternatives)
1095-
.foreach(addForwarder(name, _, span)) // ignore if any are not added
1096-
1097-
def addWildcardForwarders(seen: List[TermName], span: Span): Unit =
1098-
val nonContextual = mutable.HashSet(seen: _*)
1099-
for mbr <- path.tpe.membersBasedOnFlags(required = EmptyFlags, excluded = PrivateOrSynthetic) do
1100-
if !mbr.symbol.isSuperAccessor then
1101-
// Scala 2 superaccessors have neither Synthetic nor Artfact set, so we
1102-
// need to filter them out here (by contrast, Scala 3 superaccessors are Artifacts)
1103-
val alias = mbr.name.toTermName
1104-
if mbr.symbol.is(Given) then
1105-
if !seen.contains(alias) && mbr.matchesImportBound(givenBound) then
1106-
addForwarder(alias, mbr, span)
1107-
else if !nonContextual.contains(alias) && mbr.matchesImportBound(wildcardBound) then
1108-
nonContextual += alias
1109-
addWildcardForwardersNamed(alias, span)
1110-
1111-
def addForwarders(sels: List[untpd.ImportSelector], seen: List[TermName]): Unit = sels match
1112-
case sel :: sels1 =>
1113-
if sel.isWildcard then
1114-
addWildcardForwarders(seen, sel.span)
1115-
else
1116-
if sel.rename != nme.WILDCARD then
1117-
addForwardersNamed(sel.name, sel.rename, sel.span)
1118-
addForwarders(sels1, sel.name :: seen)
1119-
case _ =>
1086+
def addWildcardForwardersNamed(name: TermName, span: Span): Unit =
1087+
List(name, name.toTypeName)
1088+
.flatMap(path.tpe.memberBasedOnFlags(_, excluded = Private|Given|ConstructorProxy).alternatives)
1089+
.foreach(addForwarder(name, _, span)) // ignore if any are not added
1090+
1091+
def addWildcardForwarders(seen: List[TermName], span: Span): Unit =
1092+
val nonContextual = mutable.HashSet(seen: _*)
1093+
for mbr <- path.tpe.membersBasedOnFlags(required = EmptyFlags, excluded = PrivateOrSynthetic) do
1094+
if !mbr.symbol.isSuperAccessor then
1095+
// Scala 2 superaccessors have neither Synthetic nor Artfact set, so we
1096+
// need to filter them out here (by contrast, Scala 3 superaccessors are Artifacts)
1097+
val alias = mbr.name.toTermName
1098+
if mbr.symbol.is(Given) then
1099+
if !seen.contains(alias) && mbr.matchesImportBound(givenBound) then
1100+
addForwarder(alias, mbr, span)
1101+
else if !nonContextual.contains(alias) && mbr.matchesImportBound(wildcardBound) then
1102+
nonContextual += alias
1103+
addWildcardForwardersNamed(alias, span)
1104+
1105+
def addForwarders(sels: List[untpd.ImportSelector], seen: List[TermName]): Unit = sels match
1106+
case sel :: sels1 =>
1107+
if sel.isWildcard then
1108+
addWildcardForwarders(seen, sel.span)
1109+
else
1110+
if sel.rename != nme.WILDCARD then
1111+
addForwardersNamed(sel.name, sel.rename, sel.span)
1112+
addForwarders(sels1, sel.name :: seen)
1113+
case _ =>
11201114

1121-
addForwarders(selectors, Nil)
1122-
val forwarders = buf.toList
1123-
exp.pushAttachment(ExportForwarders, forwarders)
1124-
forwarders
1125-
}
1115+
addForwarders(selectors, Nil)
1116+
val forwarders = buf.toList
1117+
exp.pushAttachment(ExportForwarders, forwarders)
1118+
forwarders
1119+
end exportForwarders
11261120

1127-
for case exp @ Export(_, _) <- rest do
1128-
for forwarder <- exportForwarders(exp) do
1129-
forwarder.symbol.entered
1130-
}
1121+
/** Add forwarders as required by the export statements in this class */
1122+
private def processExports(using Context): Unit =
1123+
1124+
def process(stats: List[Tree])(using Context): Unit = stats match
1125+
case (stat: Export) :: stats1 =>
1126+
for forwarder <- exportForwarders(stat) do
1127+
forwarder.symbol.entered
1128+
process(stats1)
1129+
case (stat: Import) :: stats1 =>
1130+
process(stats1)(using ctx.importContext(stat, symbolOfTree(stat)))
1131+
case stat :: stats1 =>
1132+
process(stats1)
1133+
case Nil =>
1134+
1135+
// Do a quick scan whether we need to process at all. This avoids creating
1136+
// import contexts for nothing.
1137+
if rest.exists(_.isInstanceOf[Export]) then
1138+
process(rest)
1139+
end processExports
11311140

11321141
/** Ensure constructor is completed so that any parameter accessors
11331142
* which have type trees deriving from its parameters can be

docs/docs/reference/other-new-features/export.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,7 @@ Export clauses are processed when the type information of the enclosing object o
168168

169169
With export clauses, the following steps are added:
170170

171-
6. Compute the types of all paths in export clauses in a context logically
172-
inside the class but not considering any imports or exports in that class.
171+
6. Compute the types of all paths in export clauses.
173172
7. Enter export aliases for the eligible members of all paths in export clauses.
174173

175174
It is important that steps 6 and 7 are done in sequence: We first compute the types of _all_

tests/pos/i12112.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
object A:
2+
object B:
3+
object C
4+
5+
object X {
6+
import A.B
7+
8+
B.C // ok
9+
export B.C // error
10+
}

0 commit comments

Comments
 (0)