Skip to content

Commit bbc0df7

Browse files
Merge pull request #6729 from dotty-staging/change-export-rules
Change rules for exports to make them more useful for enums
2 parents cba756d + 6edfe83 commit bbc0df7

File tree

10 files changed

+58
-20
lines changed

10 files changed

+58
-20
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ object DesugarEnums {
9595
private def enumScaffolding(implicit ctx: Context): List[Tree] = {
9696
val valuesDef =
9797
DefDef(nme.values, Nil, Nil, TypeTree(), Select(valuesDot(nme.values), nme.toArray))
98+
.withFlags(Synthetic)
9899
val privateValuesDef =
99100
ValDef(nme.DOLLAR_VALUES, TypeTree(),
100101
New(TypeTree(defn.EnumValuesType.appliedTo(enumClass.typeRef :: Nil)), ListOfNil))
@@ -114,6 +115,7 @@ object DesugarEnums {
114115
)
115116
val valueOfDef = DefDef(nme.valueOf, Nil, List(param(nme.nameDollar, defn.StringType) :: Nil),
116117
TypeTree(), valuesOfBody)
118+
.withFlags(Synthetic)
117119

118120
valuesDef ::
119121
privateValuesDef ::

compiler/src/dotty/tools/dotc/core/SymDenotations.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1251,6 +1251,9 @@ object SymDenotations {
12511251
else defn.RootClass
12521252
}
12531253

1254+
final def isPublic(implicit ctx: Context): Boolean =
1255+
accessBoundary(owner) == defn.RootClass
1256+
12541257
/** The primary constructor of a class or trait, NoSymbol if not applicable. */
12551258
def primaryConstructor(implicit ctx: Context): Symbol = NoSymbol
12561259

compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -844,7 +844,7 @@ class TreeUnpickler(reader: TastyReader,
844844
goto(end)
845845
setSpan(start, tree)
846846
if (!sym.isType) { // Only terms might have leaky aliases, see the documentation of `checkNoPrivateLeaks`
847-
sym.info = ta.avoidPrivateLeaks(sym, tree.sourcePos)
847+
sym.info = ta.avoidPrivateLeaks(sym)
848848
}
849849

850850
if (ctx.mode.is(Mode.ReadComments)) {

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ object Checking {
470470
*
471471
* @return The `info` of `sym`, with problematic aliases expanded away.
472472
*/
473-
def checkNoPrivateLeaks(sym: Symbol, pos: SourcePosition)(implicit ctx: Context): Type = {
473+
def checkNoPrivateLeaks(sym: Symbol)(implicit ctx: Context): Type = {
474474
class NotPrivate extends TypeMap {
475475
var errors: List[() => String] = Nil
476476

@@ -499,11 +499,12 @@ object Checking {
499499
var tp1 =
500500
if (isLeaked(tp.symbol)) {
501501
errors =
502-
(() => em"non-private $sym refers to private ${tp.symbol}\nin its type signature ${sym.info}") :: errors
502+
(() => em"non-private ${sym.showLocated} refers to private ${tp.symbol}\nin its type signature ${sym.info}") ::
503+
errors
503504
tp
504505
}
505506
else mapOver(tp)
506-
if ((errors ne prevErrors) && !sym.isType && tp.info.isTypeAlias) {
507+
if ((errors ne prevErrors) && tp.info.isTypeAlias) {
507508
// try to dealias to avoid a leak error
508509
val savedErrors = errors
509510
errors = prevErrors
@@ -531,7 +532,7 @@ object Checking {
531532
}
532533
val notPrivate = new NotPrivate
533534
val info = notPrivate(sym.info)
534-
notPrivate.errors.foreach(error => ctx.errorOrMigrationWarning(error(), pos))
535+
notPrivate.errors.foreach(error => ctx.errorOrMigrationWarning(error(), sym.sourcePos))
535536
info
536537
}
537538

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

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -873,7 +873,7 @@ class Namer { typer: Typer =>
873873
denot.info = typeSig(sym)
874874
invalidateIfClashingSynthetic(denot)
875875
Checking.checkWellFormed(sym)
876-
denot.info = avoidPrivateLeaks(sym, sym.sourcePos)
876+
denot.info = avoidPrivateLeaks(sym)
877877
}
878878
}
879879

@@ -976,13 +976,15 @@ class Namer { typer: Typer =>
976976
fwdInfo(path.tpe.select(mbr.symbol), mbr.info),
977977
coord = span)
978978
else {
979-
val maybeStable = if (mbr.symbol.isStableMember) StableRealizable else EmptyFlags
980-
ctx.newSymbol(
981-
cls, alias,
982-
Exported | Method | Final | maybeStable | mbr.symbol.flags & RetainedExportFlags,
983-
mbr.info.ensureMethodic,
984-
coord = span)
979+
val (maybeStable, mbrInfo) =
980+
if (mbr.symbol.isStableMember && mbr.symbol.isPublic)
981+
(StableRealizable, ExprType(path.tpe.select(mbr.symbol)))
982+
else
983+
(EmptyFlags, mbr.info.ensureMethodic)
984+
val mbrFlags = Exported | Method | Final | maybeStable | mbr.symbol.flags & RetainedExportFlags
985+
ctx.newSymbol(cls, alias, mbrFlags, mbrInfo, coord = span)
985986
}
987+
forwarder.info = avoidPrivateLeaks(forwarder)
986988
val forwarderDef =
987989
if (forwarder.isType) tpd.TypeDef(forwarder.asType)
988990
else {
@@ -1010,7 +1012,8 @@ class Namer { typer: Typer =>
10101012
}
10111013

10121014
def addForwardersExcept(seen: List[TermName], span: Span): Unit =
1013-
for (mbr <- path.tpe.allMembers) {
1015+
for (mbr <- path.tpe.membersBasedOnFlags(
1016+
required = EmptyFlags, excluded = PrivateOrSynthetic)) {
10141017
val alias = mbr.name.toTermName
10151018
if (!seen.contains(alias)) addForwarder(alias, mbr, span)
10161019
}
@@ -1146,7 +1149,7 @@ class Namer { typer: Typer =>
11461149

11471150
Checking.checkWellFormed(cls)
11481151
if (isDerivedValueClass(cls)) cls.setFlag(Final)
1149-
cls.info = avoidPrivateLeaks(cls, cls.sourcePos)
1152+
cls.info = avoidPrivateLeaks(cls)
11501153
cls.baseClasses.foreach(_.invalidateBaseTypeCache()) // we might have looked before and found nothing
11511154
cls.setNoInitsFlags(parentsKind(parents), bodyKind(rest))
11521155
if (cls.isNoInitsClass) cls.primaryConstructor.setFlag(StableRealizable)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,8 @@ trait TypeAssigner {
155155
def avoidingType(expr: Tree, bindings: List[Tree])(implicit ctx: Context): Type =
156156
avoid(expr.tpe, localSyms(bindings).filter(_.isTerm))
157157

158-
def avoidPrivateLeaks(sym: Symbol, pos: SourcePosition)(implicit ctx: Context): Type =
159-
if (!sym.isOneOf(PrivateOrSynthetic) && sym.owner.isClass) checkNoPrivateLeaks(sym, pos)
158+
def avoidPrivateLeaks(sym: Symbol)(implicit ctx: Context): Type =
159+
if (!sym.isOneOf(PrivateOrSynthetic) && sym.owner.isClass) checkNoPrivateLeaks(sym)
160160
else sym.info
161161

162162
private def toRepeated(tree: Tree, from: ClassSymbol)(implicit ctx: Context): Tree =

docs/docs/reference/enums/desugarEnums.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ map into case classes or vals.
157157

158158
Non-generic enums `E` that define one or more singleton cases
159159
are called _enumerations_. Companion objects of enumerations define
160-
the following additional members.
160+
the following additional synthetic members.
161161

162162
- A method `valueOf(name: String): E`. It returns the singleton case value whose
163163
`toString` representation is `name`.

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ of one of the following forms:
5454
- An _omitting selector_ `x => _` prevents `x` from being aliased by a subsequent
5555
wildcard selector.
5656
- A _wildcard selector_ creates aliases for all eligible members of `path` except for
57-
those members that are named by a previous simple, renaming, or omitting selector.
57+
synthetic members generated by the compiler and those members that are named by a previous simple, renaming, or omitting selector.
5858

5959
A member is _eligible_ if all of the following holds:
6060

@@ -75,8 +75,7 @@ Export aliases are always `final`. Aliases of delegates are again defines as del
7575
not marked `override`.
7676
- However, export aliases can implement deferred members of base classes.
7777

78-
Export aliases for value definitions are marked by the compiler as "stable". This means
79-
that they can be used as parts of stable identifier paths, even though they are technically methods. For instance, the following is OK:
78+
Export aliases for public value definitions are marked by the compiler as "stable" and their result types are the singleton types of the aliased definitions. This means that they can be used as parts of stable identifier paths, even though they are technically methods. For instance, the following is OK:
8079
```scala
8180
class C { type T }
8281
object O { val c: C = ... }

tests/neg/export-leaks.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Check that exports do not leak private types
2+
object Signature {
3+
4+
private type T
5+
6+
object O1 {
7+
private[Signature] def bar: T = ???
8+
}
9+
export O1._ // error: non-private method bar refers to private type T
10+
11+
object O2 {
12+
private[Signature] val foo: T = ???
13+
}
14+
export O2._ // error: non-private method foo refers to private type T
15+
}

tests/pos/export-enum.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
object Signature {
2+
3+
enum MatchDegree {
4+
case NoMatch, ParamMatch, FullMatch
5+
}
6+
export MatchDegree._
7+
8+
// Check that exported values have singeleton types
9+
val x: MatchDegree.NoMatch.type = NoMatch
10+
11+
// Check that the following two methods are not exported.
12+
// Exporting them would lead to a double definition.
13+
def values: Array[MatchDegree] = ???
14+
def valueOf($name: String): MatchDegree = ???
15+
}

0 commit comments

Comments
 (0)