Skip to content

Commit 5e8eb74

Browse files
bracevacjan-pieterwwbakkerodersky
committed
Prevent Cyclic Exports on Current Class
A member that is visible through inheritance or mixin should not be eligible for `export`. We perform the `derivesFrom` check in `canForward` now on the self-type of the exporting class to detect such cycles through mixins. Co-authored-by: Jan-Pieter van den Heuvel <[email protected]> Co-authored-by: Willem W Bakker <[email protected]> Co-authored-by: odersky <[email protected]>
1 parent 4cb43b6 commit 5e8eb74

File tree

4 files changed

+72
-2
lines changed

4 files changed

+72
-2
lines changed

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

+16-1
Original file line numberDiff line numberDiff line change
@@ -1175,11 +1175,26 @@ class Namer { typer: Typer =>
11751175
def canForward(mbr: SingleDenotation, alias: TermName): CanForward = {
11761176
import CanForward.*
11771177
val sym = mbr.symbol
1178+
/**
1179+
* The export selects a member of the current class (issue #22147).
1180+
* Assumes that cls.classInfo.selfType.derivesFrom(sym.owner) is true.
1181+
*/
1182+
def isCurrentClassMember: Boolean = expr match
1183+
case id: (Ident | This) => // Access through self type or this
1184+
/* Given the usage context below, where cls's self type is a subtype of sym.owner,
1185+
it suffices to check if symbol is the same class. */
1186+
cls == id.symbol
1187+
case _ => false
11781188
if !sym.isAccessibleFrom(pathType) then
11791189
No("is not accessible")
11801190
else if sym.isConstructor || sym.is(ModuleClass) || sym.is(Bridge) || sym.is(ConstructorProxy) || sym.isAllOf(JavaModule) then
11811191
Skip
1182-
else if cls.derivesFrom(sym.owner) && (sym.owner == cls || !sym.is(Deferred)) then
1192+
// if the cls is a subclass or mixes in the owner of the symbol
1193+
// and either
1194+
// * the symbols owner is the cls itself
1195+
// * the symbol is not a deferred symbol
1196+
// * the symbol is a member of the current class (#22147)
1197+
else if cls.classInfo.selfType.derivesFrom(sym.owner) && (sym.owner == cls || !sym.is(Deferred) || isCurrentClassMember) then
11831198
No(i"is already a member of $cls")
11841199
else if pathMethod.exists && mbr.isType then
11851200
No("is a type, so it cannot be exported as extension method")

tests/neg/exports3.scala

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
trait P:
2+
def foo: Int
3+
4+
class A extends P:
5+
export this.foo // error
6+
7+
trait Q extends P:
8+
def bar: Int
9+
10+
trait R extends P:
11+
def baz: Int
12+
val a1: A
13+
val a2: A
14+
15+
abstract class B extends R:
16+
self =>
17+
export this.baz // error
18+
export self.bar // error
19+
export this.a1.foo
20+
export self.a2.foo // error
21+
export a2.foo // error
22+
23+
abstract class D extends P:
24+
val p: P
25+
export p.foo
26+
27+
abstract class E:
28+
self: P =>
29+
export self.foo // error
30+
31+
abstract class F:
32+
self: P =>
33+
export this.foo // error
34+
35+
class G(p: P):
36+
self: P =>
37+
export p.foo
38+
39+
class H(p: P):
40+
self: P =>
41+
export this.p.foo

tests/neg/i20245.check

+14
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,17 @@
1515
| Run with both -explain-cyclic and -Ydebug-cyclic to see full stack trace.
1616
|
1717
| longer explanation available when compiling with `-explain`
18+
-- [E046] Cyclic Error: tests/neg/i20245/Typer_2.scala:10:7 ------------------------------------------------------------
19+
10 |import effekt.source.{ resolve } // error
20+
| ^
21+
| Cyclic reference involving class Context
22+
|
23+
| The error occurred while trying to compute the base classes of class Context
24+
| which required to compute the base classes of trait TyperOps
25+
| which required to compute the signature of trait TyperOps
26+
| which required to elaborate the export clause export unification.requireSubtype
27+
| which required to compute the base classes of class Context
28+
|
29+
| Run with both -explain-cyclic and -Ydebug-cyclic to see full stack trace.
30+
|
31+
| longer explanation available when compiling with `-explain`

tests/neg/i20245/Typer_2.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import effekt.util.messages.ErrorReporter
77
import effekt.context.{ Context }
88

99
// This import is also NECESSARY for the cyclic error
10-
import effekt.source.{ resolve }
10+
import effekt.source.{ resolve } // error
1111

1212

1313
trait TyperOps extends ErrorReporter { self: Context =>

0 commit comments

Comments
 (0)