diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 07f19b420c1a..bc4e1a332ff6 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1177,11 +1177,26 @@ class Namer { typer: Typer => def canForward(mbr: SingleDenotation, alias: TermName): CanForward = { import CanForward.* val sym = mbr.symbol + /** + * The export selects a member of the current class (issue #22147). + * Assumes that cls.classInfo.selfType.derivesFrom(sym.owner) is true. + */ + def isCurrentClassMember: Boolean = expr match + case id: (Ident | This) => // Access through self type or this + /* Given the usage context below, where cls's self type is a subtype of sym.owner, + it suffices to check if symbol is the same class. */ + cls == id.symbol + case _ => false if !sym.isAccessibleFrom(pathType) then No("is not accessible") else if sym.isConstructor || sym.is(ModuleClass) || sym.is(Bridge) || sym.is(ConstructorProxy) || sym.isAllOf(JavaModule) then Skip - else if cls.derivesFrom(sym.owner) && (sym.owner == cls || !sym.is(Deferred)) then + // if the cls is a subclass or mixes in the owner of the symbol + // and either + // * the symbols owner is the cls itself + // * the symbol is not a deferred symbol + // * the symbol is a member of the current class (#22147) + else if cls.classInfo.selfType.derivesFrom(sym.owner) && (sym.owner == cls || !sym.is(Deferred) || isCurrentClassMember) then No(i"is already a member of $cls") else if pathMethod.exists && mbr.isType then No("is a type, so it cannot be exported as extension method") diff --git a/tests/neg/exports3.scala b/tests/neg/exports3.scala new file mode 100644 index 000000000000..eaea93d9f7ce --- /dev/null +++ b/tests/neg/exports3.scala @@ -0,0 +1,41 @@ +trait P: + def foo: Int + +class A extends P: + export this.foo // error + +trait Q extends P: + def bar: Int + +trait R extends P: + def baz: Int + val a1: A + val a2: A + +abstract class B extends R: + self => + export this.baz // error + export self.bar // error + export this.a1.foo + export self.a2.foo // error + export a2.foo // error + +abstract class D extends P: + val p: P + export p.foo + +abstract class E: + self: P => + export self.foo // error + +abstract class F: + self: P => + export this.foo // error + +class G(p: P): + self: P => + export p.foo + +class H(p: P): + self: P => + export this.p.foo \ No newline at end of file diff --git a/tests/neg/i20245.check b/tests/neg/i20245.check index 565bde7678b7..49d08c646f99 100644 --- a/tests/neg/i20245.check +++ b/tests/neg/i20245.check @@ -15,3 +15,17 @@ | Run with both -explain-cyclic and -Ydebug-cyclic to see full stack trace. | | longer explanation available when compiling with `-explain` +-- [E046] Cyclic Error: tests/neg/i20245/Typer_2.scala:10:7 ------------------------------------------------------------ +10 |import effekt.source.{ resolve } // error + | ^ + | Cyclic reference involving class Context + | + | The error occurred while trying to compute the base classes of class Context + | which required to compute the base classes of trait TyperOps + | which required to compute the signature of trait TyperOps + | which required to elaborate the export clause export unification.requireSubtype + | which required to compute the base classes of class Context + | + | Run with both -explain-cyclic and -Ydebug-cyclic to see full stack trace. + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i20245/Typer_2.scala b/tests/neg/i20245/Typer_2.scala index ed7f05de80d0..bf4718de759c 100644 --- a/tests/neg/i20245/Typer_2.scala +++ b/tests/neg/i20245/Typer_2.scala @@ -7,7 +7,7 @@ import effekt.util.messages.ErrorReporter import effekt.context.{ Context } // This import is also NECESSARY for the cyclic error -import effekt.source.{ resolve } +import effekt.source.{ resolve } // error trait TyperOps extends ErrorReporter { self: Context =>