Skip to content

Commit 6f36dea

Browse files
authored
Merge pull request #245 from scala/backport-lts-3.3-22545
Backport "Rule Out Exports of Member of the Current Class" to 3.3 LTS
2 parents f4223d8 + 5a344b9 commit 6f36dea

File tree

8 files changed

+130
-8
lines changed

8 files changed

+130
-8
lines changed

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import config.Feature
3333
import config.Feature.{sourceVersion, migrateTo3, globalOnlyImports}
3434
import config.SourceVersion.*
3535
import config.SourceVersion
36+
import dotty.tools.dotc.util.chaining.*
3637

3738
object Parsers {
3839

@@ -1020,10 +1021,11 @@ object Parsers {
10201021
*/
10211022
def followingIsLambdaAfterColon(): Boolean =
10221023
val lookahead = in.LookaheadScanner(allowIndent = true)
1024+
.tap(_.currentRegion.knownWidth = in.currentRegion.indentWidth)
10231025
def isArrowIndent() =
10241026
lookahead.isArrow
10251027
&& {
1026-
lookahead.nextToken()
1028+
lookahead.observeArrowIndented()
10271029
lookahead.token == INDENT || lookahead.token == EOF
10281030
}
10291031
lookahead.nextToken()

compiler/src/dotty/tools/dotc/parsing/Scanners.scala

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,6 @@ object Scanners {
636636
insert(OUTDENT, offset)
637637
else if r.isInstanceOf[InBraces] && !closingRegionTokens.contains(token) then
638638
report.warning("Line is indented too far to the left, or a `}` is missing", sourcePos())
639-
640639
else if lastWidth < nextWidth
641640
|| lastWidth == nextWidth && (lastToken == MATCH || lastToken == CATCH) && token == CASE then
642641
if canStartIndentTokens.contains(lastToken) then
@@ -656,7 +655,7 @@ object Scanners {
656655
def spaceTabMismatchMsg(lastWidth: IndentWidth, nextWidth: IndentWidth): Message =
657656
em"""Incompatible combinations of tabs and spaces in indentation prefixes.
658657
|Previous indent : $lastWidth
659-
|Latest indent : $nextWidth"""
658+
|Latest indent : $nextWidth"""
660659

661660
def observeColonEOL(inTemplate: Boolean): Unit =
662661
val enabled =
@@ -670,6 +669,23 @@ object Scanners {
670669
reset()
671670
if atEOL then token = COLONeol
672671

672+
// consume => and insert <indent> if applicable. Used to detect colon arrow: x =>
673+
def observeArrowIndented(): Unit =
674+
if isArrow && indentSyntax then
675+
peekAhead()
676+
val atEOL = isAfterLineEnd
677+
val atEOF = token == EOF
678+
reset()
679+
if atEOF then
680+
token = EOF
681+
else if atEOL then
682+
val nextWidth = indentWidth(next.offset)
683+
val lastWidth = currentRegion.indentWidth
684+
if lastWidth < nextWidth then
685+
currentRegion = Indented(nextWidth, COLONeol, currentRegion)
686+
offset = next.offset
687+
token = INDENT
688+
673689
def observeIndented(): Unit =
674690
if indentSyntax && isNewLine then
675691
val nextWidth = indentWidth(next.offset)
@@ -1098,7 +1114,7 @@ object Scanners {
10981114
reset()
10991115
next
11001116

1101-
class LookaheadScanner(val allowIndent: Boolean = false) extends Scanner(source, offset, allowIndent = allowIndent) {
1117+
class LookaheadScanner(allowIndent: Boolean = false) extends Scanner(source, offset, allowIndent = allowIndent) {
11021118
override protected def initialCharBufferSize = 8
11031119
override def languageImportContext = Scanner.this.languageImportContext
11041120
}
@@ -1650,7 +1666,7 @@ object Scanners {
16501666
case class InCase(outer: Region) extends Region(OUTDENT)
16511667

16521668
/** A class describing an indentation region.
1653-
* @param width The principal indendation width
1669+
* @param width The principal indentation width
16541670
* @param prefix The token before the initial <indent> of the region
16551671
*/
16561672
case class Indented(width: IndentWidth, prefix: Token, outer: Region | Null) extends Region(OUTDENT):

compiler/src/dotty/tools/dotc/semanticdb/SemanticSymbolBuilder.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,12 @@ class SemanticSymbolBuilder:
108108
else if (sym.isScala2PackageObject) then
109109
b.append(Symbols.PackageObjectDescriptor)
110110
else
111+
def isScalaMethodOrVar = sym.isOneOf(Method | Mutable) && !sym.is(JavaDefined)
112+
def isJavaMethod = sym.is(Method) && sym.is(JavaDefined)
111113
addName(b, sym.name)
112114
if sym.is(Package) then b.append('/')
113115
else if sym.isType || sym.isAllOf(JavaModule) then b.append('#')
114-
else if sym.is(Method) || (sym.is(Mutable) && !sym.is(JavaDefined))
116+
else if (isScalaMethodOrVar || isJavaMethod)
115117
&& (!sym.is(StableRealizable) || sym.isConstructor) then
116118
b.append('('); addOverloadIdx(sym); b.append(").")
117119
else b.append('.')

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1130,11 +1130,26 @@ class Namer { typer: Typer =>
11301130
def canForward(mbr: SingleDenotation, alias: TermName): CanForward = {
11311131
import CanForward.*
11321132
val sym = mbr.symbol
1133+
/**
1134+
* The export selects a member of the current class (issue #22147).
1135+
* Assumes that cls.classInfo.selfType.derivesFrom(sym.owner) is true.
1136+
*/
1137+
def isCurrentClassMember: Boolean = expr match
1138+
case id: (Ident | This) => // Access through self type or this
1139+
/* Given the usage context below, where cls's self type is a subtype of sym.owner,
1140+
it suffices to check if symbol is the same class. */
1141+
cls == id.symbol
1142+
case _ => false
11331143
if !sym.isAccessibleFrom(pathType) then
11341144
No("is not accessible")
11351145
else if sym.isConstructor || sym.is(ModuleClass) || sym.is(Bridge) || sym.is(ConstructorProxy) || sym.isAllOf(JavaModule) then
11361146
Skip
1137-
else if cls.derivesFrom(sym.owner) && (sym.owner == cls || !sym.is(Deferred)) then
1147+
// if the cls is a subclass or mixes in the owner of the symbol
1148+
// and either
1149+
// * the symbols owner is the cls itself
1150+
// * the symbol is not a deferred symbol
1151+
// * the symbol is a member of the current class (#22147)
1152+
else if cls.classInfo.selfType.derivesFrom(sym.owner) && (sym.owner == cls || !sym.is(Deferred) || isCurrentClassMember) then
11381153
No(i"is already a member of $cls")
11391154
else if pathMethod.exists && mbr.isType then
11401155
No("is a type, so it cannot be exported as extension method")

tests/neg/exports3.scala

Lines changed: 41 additions & 0 deletions
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

Lines changed: 14 additions & 0 deletions
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

Lines changed: 1 addition & 1 deletion
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 =>

tests/neg/i22193.scala

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
def fn2(arg: String, arg2: String)(f: String => Unit): Unit = f(arg)
3+
4+
def fn3(arg: String, arg2: String)(f: => Unit): Unit = f
5+
6+
def test1() =
7+
8+
// ok baseline
9+
fn2(arg = "blue sleeps faster than tuesday", arg2 = "the quick brown fox jumped over the lazy dog"): env =>
10+
val x = env
11+
println(x)
12+
13+
fn2( // error not a legal formal parameter for a function literal
14+
arg = "blue sleeps faster than tuesday",
15+
arg2 = "the quick brown fox jumped over the lazy dog"): env =>
16+
val x = env // error
17+
println(x)
18+
19+
fn2(
20+
arg = "blue sleeps faster than tuesday",
21+
arg2 = "the quick brown fox jumped over the lazy dog"):
22+
env => // error indented definitions expected, identifier env found
23+
val x = env
24+
println(x)
25+
26+
def test2() =
27+
28+
fn3( // error missing argument list for value of type (=> Unit) => Unit
29+
arg = "blue sleeps faster than tuesday",
30+
arg2 = "the quick brown fox jumped over the lazy dog"):
31+
val x = "Hello" // error
32+
println(x) // error

0 commit comments

Comments
 (0)