Skip to content

Commit f11239e

Browse files
committed
Check exports for private leaks
1 parent 03e1aee commit f11239e

File tree

6 files changed

+45
-7
lines changed

6 files changed

+45
-7
lines changed

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: 2 additions & 2 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

@@ -531,7 +531,7 @@ object Checking {
531531
}
532532
val notPrivate = new NotPrivate
533533
val info = notPrivate(sym.info)
534-
notPrivate.errors.foreach(error => ctx.errorOrMigrationWarning(error(), pos))
534+
notPrivate.errors.foreach(error => ctx.errorOrMigrationWarning(error(), sym.sourcePos))
535535
info
536536
}
537537

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

Lines changed: 3 additions & 2 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

@@ -982,6 +982,7 @@ class Namer { typer: Typer =>
982982
val mbrFlags = Exported | Method | Final | maybeStable | mbr.symbol.flags & RetainedExportFlags
983983
ctx.newSymbol(cls, alias, mbrFlags, mbrInfo, coord = span)
984984
}
985+
forwarder.info = avoidPrivateLeaks(forwarder)
985986
val forwarderDef =
986987
if (forwarder.isType) tpd.TypeDef(forwarder.asType)
987988
else {
@@ -1146,7 +1147,7 @@ class Namer { typer: Typer =>
11461147

11471148
Checking.checkWellFormed(cls)
11481149
if (isDerivedValueClass(cls)) cls.setFlag(Final)
1149-
cls.info = avoidPrivateLeaks(cls, cls.sourcePos)
1150+
cls.info = avoidPrivateLeaks(cls)
11501151
cls.baseClasses.foreach(_.invalidateBaseTypeCache()) // we might have looked before and found nothing
11511152
cls.setNoInitsFlags(parentsKind(parents), bodyKind(rest))
11521153
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 =

tests/neg/export-leaks.scala

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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._ // OK
15+
// The reason this works is that private escape checking only looks at real private members,
16+
// not at private[C] members. So, by itself the expansion of the export
17+
// <stable> def foo: O2.foo.type = O2.foo
18+
// is legal. The underlying type of `O2.foo.type` does violate no-escape rules, but we do not
19+
// check for it. Maybe we should. But then the question comes up whether we should
20+
// also check all possible supertypes of a type for privacy violations. These are more
21+
// general questions that are not related to exports.
22+
}

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)