diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 4bd7769187d5..4354be9662be 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -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 { @@ -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() diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 57a7ff1c4a9b..96e9cd600363 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -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 @@ -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 = @@ -670,6 +669,23 @@ object Scanners { reset() if atEOL then token = COLONeol + // consume => and insert 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) @@ -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 } @@ -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 of the region */ case class Indented(width: IndentWidth, prefix: Token, outer: Region | Null) extends Region(OUTDENT): diff --git a/compiler/src/dotty/tools/dotc/semanticdb/SemanticSymbolBuilder.scala b/compiler/src/dotty/tools/dotc/semanticdb/SemanticSymbolBuilder.scala index 077ee0e40b86..8e7e0ccc2c4b 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/SemanticSymbolBuilder.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/SemanticSymbolBuilder.scala @@ -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('.') diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 0471bb6a432a..cea045cecc35 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -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") 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 => diff --git a/tests/neg/i22193.scala b/tests/neg/i22193.scala new file mode 100644 index 000000000000..f7ee5b1cf5e1 --- /dev/null +++ b/tests/neg/i22193.scala @@ -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