Skip to content

Change rules for exports to make them more useful for enums #6729

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 6 commits into from
Jul 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ object DesugarEnums {
private def enumScaffolding(implicit ctx: Context): List[Tree] = {
val valuesDef =
DefDef(nme.values, Nil, Nil, TypeTree(), Select(valuesDot(nme.values), nme.toArray))
.withFlags(Synthetic)
val privateValuesDef =
ValDef(nme.DOLLAR_VALUES, TypeTree(),
New(TypeTree(defn.EnumValuesType.appliedTo(enumClass.typeRef :: Nil)), ListOfNil))
Expand All @@ -114,6 +115,7 @@ object DesugarEnums {
)
val valueOfDef = DefDef(nme.valueOf, Nil, List(param(nme.nameDollar, defn.StringType) :: Nil),
TypeTree(), valuesOfBody)
.withFlags(Synthetic)

valuesDef ::
privateValuesDef ::
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1251,6 +1251,9 @@ object SymDenotations {
else defn.RootClass
}

final def isPublic(implicit ctx: Context): Boolean =
accessBoundary(owner) == defn.RootClass

/** The primary constructor of a class or trait, NoSymbol if not applicable. */
def primaryConstructor(implicit ctx: Context): Symbol = NoSymbol

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -844,7 +844,7 @@ class TreeUnpickler(reader: TastyReader,
goto(end)
setSpan(start, tree)
if (!sym.isType) { // Only terms might have leaky aliases, see the documentation of `checkNoPrivateLeaks`
sym.info = ta.avoidPrivateLeaks(sym, tree.sourcePos)
sym.info = ta.avoidPrivateLeaks(sym)
}

if (ctx.mode.is(Mode.ReadComments)) {
Expand Down
9 changes: 5 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ object Checking {
*
* @return The `info` of `sym`, with problematic aliases expanded away.
*/
def checkNoPrivateLeaks(sym: Symbol, pos: SourcePosition)(implicit ctx: Context): Type = {
def checkNoPrivateLeaks(sym: Symbol)(implicit ctx: Context): Type = {
class NotPrivate extends TypeMap {
var errors: List[() => String] = Nil

Expand Down Expand Up @@ -499,11 +499,12 @@ object Checking {
var tp1 =
if (isLeaked(tp.symbol)) {
errors =
(() => em"non-private $sym refers to private ${tp.symbol}\nin its type signature ${sym.info}") :: errors
(() => em"non-private ${sym.showLocated} refers to private ${tp.symbol}\nin its type signature ${sym.info}") ::
errors
tp
}
else mapOver(tp)
if ((errors ne prevErrors) && !sym.isType && tp.info.isTypeAlias) {
if ((errors ne prevErrors) && tp.info.isTypeAlias) {
// try to dealias to avoid a leak error
val savedErrors = errors
errors = prevErrors
Expand Down Expand Up @@ -531,7 +532,7 @@ object Checking {
}
val notPrivate = new NotPrivate
val info = notPrivate(sym.info)
notPrivate.errors.foreach(error => ctx.errorOrMigrationWarning(error(), pos))
notPrivate.errors.foreach(error => ctx.errorOrMigrationWarning(error(), sym.sourcePos))
info
}

Expand Down
21 changes: 12 additions & 9 deletions compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -873,7 +873,7 @@ class Namer { typer: Typer =>
denot.info = typeSig(sym)
invalidateIfClashingSynthetic(denot)
Checking.checkWellFormed(sym)
denot.info = avoidPrivateLeaks(sym, sym.sourcePos)
denot.info = avoidPrivateLeaks(sym)
}
}

Expand Down Expand Up @@ -976,13 +976,15 @@ class Namer { typer: Typer =>
fwdInfo(path.tpe.select(mbr.symbol), mbr.info),
coord = span)
else {
val maybeStable = if (mbr.symbol.isStableMember) StableRealizable else EmptyFlags
ctx.newSymbol(
cls, alias,
Exported | Method | Final | maybeStable | mbr.symbol.flags & RetainedExportFlags,
mbr.info.ensureMethodic,
coord = span)
val (maybeStable, mbrInfo) =
if (mbr.symbol.isStableMember && mbr.symbol.isPublic)
(StableRealizable, ExprType(path.tpe.select(mbr.symbol)))
else
(EmptyFlags, mbr.info.ensureMethodic)
val mbrFlags = Exported | Method | Final | maybeStable | mbr.symbol.flags & RetainedExportFlags
ctx.newSymbol(cls, alias, mbrFlags, mbrInfo, coord = span)
}
forwarder.info = avoidPrivateLeaks(forwarder)
val forwarderDef =
if (forwarder.isType) tpd.TypeDef(forwarder.asType)
else {
Expand Down Expand Up @@ -1010,7 +1012,8 @@ class Namer { typer: Typer =>
}

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

Checking.checkWellFormed(cls)
if (isDerivedValueClass(cls)) cls.setFlag(Final)
cls.info = avoidPrivateLeaks(cls, cls.sourcePos)
cls.info = avoidPrivateLeaks(cls)
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)
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ trait TypeAssigner {
def avoidingType(expr: Tree, bindings: List[Tree])(implicit ctx: Context): Type =
avoid(expr.tpe, localSyms(bindings).filter(_.isTerm))

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

private def toRepeated(tree: Tree, from: ClassSymbol)(implicit ctx: Context): Tree =
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/reference/enums/desugarEnums.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ map into case classes or vals.

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

- A method `valueOf(name: String): E`. It returns the singleton case value whose
`toString` representation is `name`.
Expand Down
5 changes: 2 additions & 3 deletions docs/docs/reference/other-new-features/export.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ of one of the following forms:
- An _omitting selector_ `x => _` prevents `x` from being aliased by a subsequent
wildcard selector.
- A _wildcard selector_ creates aliases for all eligible members of `path` except for
those members that are named by a previous simple, renaming, or omitting selector.
synthetic members generated by the compiler and those members that are named by a previous simple, renaming, or omitting selector.

A member is _eligible_ if all of the following holds:

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

Export aliases for value definitions are marked by the compiler as "stable". 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:
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:
```scala
class C { type T }
object O { val c: C = ... }
Expand Down
15 changes: 15 additions & 0 deletions tests/neg/export-leaks.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Check that exports do not leak private types
object Signature {

private type T

object O1 {
private[Signature] def bar: T = ???
}
export O1._ // error: non-private method bar refers to private type T

object O2 {
private[Signature] val foo: T = ???
}
export O2._ // error: non-private method foo refers to private type T
}
15 changes: 15 additions & 0 deletions tests/pos/export-enum.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
object Signature {

enum MatchDegree {
case NoMatch, ParamMatch, FullMatch
}
export MatchDegree._

// Check that exported values have singeleton types
val x: MatchDegree.NoMatch.type = NoMatch

// Check that the following two methods are not exported.
// Exporting them would lead to a double definition.
def values: Array[MatchDegree] = ???
def valueOf($name: String): MatchDegree = ???
}