Skip to content

checkNoPrivateLeaks: Do not allow types to refer to leaky aliases #2189

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 1 commit into from
Apr 4, 2017
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
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -736,7 +736,9 @@ class TreeUnpickler(reader: TastyReader, tastyName: TastyName.Table, posUnpickle
// no longer necessary.
goto(end)
setPos(start, tree)
sym.info = ta.avoidPrivateLeaks(sym, tree.pos)
if (!sym.isType) { // Only terms might have leaky aliases, see the documentation of `checkNoPrivateLeaks`
sym.info = ta.avoidPrivateLeaks(sym, tree.pos)
}
tree
}

Expand Down
23 changes: 17 additions & 6 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -348,12 +348,23 @@ object Checking {

/** Check the type signature of the symbol `M` defined by `tree` does not refer
* to a private type or value which is invisible at a point where `M` is still
* visible. As an exception, we allow references to type aliases if the underlying
* type of the alias is not a leak. So type aliases are transparent as far as
* leak testing is concerned.
* visible.
*
* As an exception, we allow references to type aliases if the underlying
* type of the alias is not a leak, and if `sym` is not a type. The rationale
* for this is that the inferred type of a term symbol might contain leaky
* aliases which should be removed (see leak-inferred.scala for an example),
* but a type symbol definition will not contain leaky aliases unless the
* user wrote them, so we can ask the user to change his definition. The more
* practical reason for not transforming types is that `checkNoPrivateLeaks`
* can force a lot of denotations, and this restriction means that we never
* need to run `TypeAssigner#avoidPrivateLeaks` on type symbols when
* unpickling, which avoids some issues related to forcing order.
*
* See i997.scala for negative tests, and i1130.scala for a case where it
* matters that we transform leaky aliases away.
*
* @return The `info` of `sym`, with problematic aliases expanded away.
* See i997.scala for tests, i1130.scala for a case where it matters that we
* transform leaky aliases away.
*/
def checkNoPrivateLeaks(sym: Symbol, pos: Position)(implicit ctx: Context): Type = {
class NotPrivate extends TypeMap {
Expand Down Expand Up @@ -388,7 +399,7 @@ object Checking {
tp
}
else mapOver(tp)
if ((errors ne prevErrors) && tp.info.isAlias) {
if ((errors ne prevErrors) && !sym.isType && tp.info.isAlias) {
// try to dealias to avoid a leak error
val savedErrors = errors
errors = prevErrors
Expand Down
13 changes: 13 additions & 0 deletions tests/neg/leak-type.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
trait A {
private type Foo = Int


class Inner[T <: Foo] { // error: non-private type T refers to private type Foo in its type signature
def get: T = ???
}
}
class B extends A {
def foo(x: Inner[_]): Unit = {
val a = x.get // error: cannot resolve reference to type B(B.this).Foo
}
}
4 changes: 3 additions & 1 deletion tests/pos/i1130.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ trait A {

def foo: Foo = 1
}
class B extends A
class B extends A {
foo
}
12 changes: 12 additions & 0 deletions tests/pos/leak-inferred.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class A {
private val x = List(1,2)

val elem = x.head
}

class B extends A {
val a: Int = elem
// Without `checkNoPrivateLeaks`, we get:
// found: B.this.x.scala$collection$immutable$List$$A(B.this.elem)
// required: Int
}