From 8f188831b894d839b2d2be8b106cd4254afc695e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Bra=C4=8Devac?= Date: Mon, 3 Feb 2025 19:18:19 +0100 Subject: [PATCH 1/4] Export: Refine canForward for selections on this --- compiler/src/dotty/tools/dotc/typer/Namer.scala | 7 ++++++- tests/neg/exports3.scala | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 tests/neg/exports3.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 21ef0fc5d123..f9a910bbd852 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1179,7 +1179,12 @@ class Namer { typer: Typer => 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 of 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 deferred symbol and the selection is on a This + else if cls.derivesFrom(sym.owner) && (sym.owner == cls || !sym.is(Deferred) || (sym.is(Deferred) && expr.isInstanceOf[This])) 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..f57e9d49a823 --- /dev/null +++ b/tests/neg/exports3.scala @@ -0,0 +1,5 @@ +trait P: + def foo: Int + +class A extends P: + export this.foo // error \ No newline at end of file From 29c9945316c72537c849167da5f3546f9b0fd43a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Bra=C4=8Devac?= Date: Mon, 3 Feb 2025 20:11:25 +0100 Subject: [PATCH 2/4] Exports: Improve detection of selections on this Fixes the typeclass-aggregates test. We now reject selections on `this` in `canForward` only if the selection has an empty qualifier. --- compiler/src/dotty/tools/dotc/typer/Namer.scala | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index f9a910bbd852..dd598163a37b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1175,6 +1175,13 @@ class Namer { typer: Typer => def canForward(mbr: SingleDenotation, alias: TermName): CanForward = { import CanForward.* val sym = mbr.symbol + /** + * Check the export selects an abstract member (issue #22147). + */ + def isAbstractMember: Boolean = sym.is(Deferred) && (expr match + case ths: This if ths.qual.isEmpty => true + 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 @@ -1183,8 +1190,8 @@ class Namer { typer: Typer => // and either // * the symbols owner is the cls itself // * the symbol is not a deferred symbol - // * the symbol is a deferred symbol and the selection is on a This - else if cls.derivesFrom(sym.owner) && (sym.owner == cls || !sym.is(Deferred) || (sym.is(Deferred) && expr.isInstanceOf[This])) then + // * the symbol is an abstract member #22147 + else if cls.derivesFrom(sym.owner) && (sym.owner == cls || !sym.is(Deferred) || isAbstractMember) 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") From bf1e938b3a5c4b4e5efb2544672816c3c28bd320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Bra=C4=8Devac?= Date: Tue, 4 Feb 2025 10:26:14 +0100 Subject: [PATCH 3/4] Update comment Co-authored-by: Jan-Pieter van den Heuvel Co-authored-by: Willem W Bakker Co-authored-by: odersky --- compiler/src/dotty/tools/dotc/typer/Namer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index dd598163a37b..8341ff16c9f1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1176,7 +1176,7 @@ class Namer { typer: Typer => import CanForward.* val sym = mbr.symbol /** - * Check the export selects an abstract member (issue #22147). + * Check the export selects an abstract member of the current class (issue #22147). */ def isAbstractMember: Boolean = sym.is(Deferred) && (expr match case ths: This if ths.qual.isEmpty => true From 30cdc577fe461561af2111b79f3200c36201c8b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Bra=C4=8Devac?= Date: Tue, 4 Feb 2025 11:20:56 +0100 Subject: [PATCH 4/4] Add selections on self types in isAbstractMember --- .../src/dotty/tools/dotc/typer/Namer.scala | 5 ++++- tests/neg/exports3.scala | 18 +++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 8341ff16c9f1..87bf0883aca4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1179,7 +1179,10 @@ class Namer { typer: Typer => * Check the export selects an abstract member of the current class (issue #22147). */ def isAbstractMember: Boolean = sym.is(Deferred) && (expr match - case ths: This if ths.qual.isEmpty => true + case ths: This if ths.qual.isEmpty => true // access through 'this' + case id: Ident => id.denot.info match // access through self type + case cls2: ClassInfo => cls2.cls == cls + case _ => false case _ => false ) if !sym.isAccessibleFrom(pathType) then diff --git a/tests/neg/exports3.scala b/tests/neg/exports3.scala index f57e9d49a823..5b15588fb0a0 100644 --- a/tests/neg/exports3.scala +++ b/tests/neg/exports3.scala @@ -2,4 +2,20 @@ trait P: def foo: Int class A extends P: - export this.foo // error \ No newline at end of file + export this.foo // error + +trait Q extends P: + def bar: Int + +trait R extends P: + def baz: Int + val a1: A + val a2: A + +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 \ No newline at end of file