Skip to content

Backport "Rule Out Exports of Member of the Current Class" to 3.3 LTS #245

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 3 commits into from
Apr 22, 2025
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/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import config.Feature
import config.Feature.{sourceVersion, migrateTo3, globalOnlyImports}
import config.SourceVersion.*
import config.SourceVersion
import dotty.tools.dotc.util.chaining.*

object Parsers {

Expand Down Expand Up @@ -1020,10 +1021,11 @@ object Parsers {
*/
def followingIsLambdaAfterColon(): Boolean =
val lookahead = in.LookaheadScanner(allowIndent = true)
.tap(_.currentRegion.knownWidth = in.currentRegion.indentWidth)
def isArrowIndent() =
lookahead.isArrow
&& {
lookahead.nextToken()
lookahead.observeArrowIndented()
lookahead.token == INDENT || lookahead.token == EOF
}
lookahead.nextToken()
Expand Down
24 changes: 20 additions & 4 deletions compiler/src/dotty/tools/dotc/parsing/Scanners.scala
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,6 @@ object Scanners {
insert(OUTDENT, offset)
else if r.isInstanceOf[InBraces] && !closingRegionTokens.contains(token) then
report.warning("Line is indented too far to the left, or a `}` is missing", sourcePos())

else if lastWidth < nextWidth
|| lastWidth == nextWidth && (lastToken == MATCH || lastToken == CATCH) && token == CASE then
if canStartIndentTokens.contains(lastToken) then
Expand All @@ -656,7 +655,7 @@ object Scanners {
def spaceTabMismatchMsg(lastWidth: IndentWidth, nextWidth: IndentWidth): Message =
em"""Incompatible combinations of tabs and spaces in indentation prefixes.
|Previous indent : $lastWidth
|Latest indent : $nextWidth"""
|Latest indent : $nextWidth"""

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

// consume => and insert <indent> if applicable. Used to detect colon arrow: x =>
def observeArrowIndented(): Unit =
if isArrow && indentSyntax then
peekAhead()
val atEOL = isAfterLineEnd
val atEOF = token == EOF
reset()
if atEOF then
token = EOF
else if atEOL then
val nextWidth = indentWidth(next.offset)
val lastWidth = currentRegion.indentWidth
if lastWidth < nextWidth then
currentRegion = Indented(nextWidth, COLONeol, currentRegion)
offset = next.offset
token = INDENT

def observeIndented(): Unit =
if indentSyntax && isNewLine then
val nextWidth = indentWidth(next.offset)
Expand Down Expand Up @@ -1098,7 +1114,7 @@ object Scanners {
reset()
next

class LookaheadScanner(val allowIndent: Boolean = false) extends Scanner(source, offset, allowIndent = allowIndent) {
class LookaheadScanner(allowIndent: Boolean = false) extends Scanner(source, offset, allowIndent = allowIndent) {
override protected def initialCharBufferSize = 8
override def languageImportContext = Scanner.this.languageImportContext
}
Expand Down Expand Up @@ -1650,7 +1666,7 @@ object Scanners {
case class InCase(outer: Region) extends Region(OUTDENT)

/** A class describing an indentation region.
* @param width The principal indendation width
* @param width The principal indentation width
* @param prefix The token before the initial <indent> of the region
*/
case class Indented(width: IndentWidth, prefix: Token, outer: Region | Null) extends Region(OUTDENT):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,12 @@ class SemanticSymbolBuilder:
else if (sym.isScala2PackageObject) then
b.append(Symbols.PackageObjectDescriptor)
else
def isScalaMethodOrVar = sym.isOneOf(Method | Mutable) && !sym.is(JavaDefined)
def isJavaMethod = sym.is(Method) && sym.is(JavaDefined)
addName(b, sym.name)
if sym.is(Package) then b.append('/')
else if sym.isType || sym.isAllOf(JavaModule) then b.append('#')
else if sym.is(Method) || (sym.is(Mutable) && !sym.is(JavaDefined))
else if (isScalaMethodOrVar || isJavaMethod)
&& (!sym.is(StableRealizable) || sym.isConstructor) then
b.append('('); addOverloadIdx(sym); b.append(").")
else b.append('.')
Expand Down
17 changes: 16 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1130,11 +1130,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")
Expand Down
41 changes: 41 additions & 0 deletions tests/neg/exports3.scala
Original file line number Diff line number Diff line change
@@ -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
14 changes: 14 additions & 0 deletions tests/neg/i20245.check
Original file line number Diff line number Diff line change
Expand Up @@ -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`
2 changes: 1 addition & 1 deletion tests/neg/i20245/Typer_2.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
Expand Down
32 changes: 32 additions & 0 deletions tests/neg/i22193.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

def fn2(arg: String, arg2: String)(f: String => Unit): Unit = f(arg)

def fn3(arg: String, arg2: String)(f: => Unit): Unit = f

def test1() =

// ok baseline
fn2(arg = "blue sleeps faster than tuesday", arg2 = "the quick brown fox jumped over the lazy dog"): env =>
val x = env
println(x)

fn2( // error not a legal formal parameter for a function literal
arg = "blue sleeps faster than tuesday",
arg2 = "the quick brown fox jumped over the lazy dog"): env =>
val x = env // error
println(x)

fn2(
arg = "blue sleeps faster than tuesday",
arg2 = "the quick brown fox jumped over the lazy dog"):
env => // error indented definitions expected, identifier env found
val x = env
println(x)

def test2() =

fn3( // error missing argument list for value of type (=> Unit) => Unit
arg = "blue sleeps faster than tuesday",
arg2 = "the quick brown fox jumped over the lazy dog"):
val x = "Hello" // error
println(x) // error