From 38653b1ecbbb1a5c72fb4f160707b9e7cba7f655 Mon Sep 17 00:00:00 2001 From: i10416 Date: Wed, 24 Jan 2024 07:00:19 +0900 Subject: [PATCH 01/40] fix(#16458): regression in xml syntax parsing xLiteral mistakenly assumed that the element just after `<` is always non-special element, but it is not true. It could be xCharData, comment, xProcInstr. [Cherry-picked 77aa36303a041a696cd50a9e7893677a5bc1027b] --- .../dotc/parsing/xml/MarkupParserCommon.scala | 8 ++++ .../dotc/parsing/xml/MarkupParsers.scala | 15 ++++++-- tests/pos/i16458.scala | 37 +++++++++++++++++++ 3 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 tests/pos/i16458.scala diff --git a/compiler/src/dotty/tools/dotc/parsing/xml/MarkupParserCommon.scala b/compiler/src/dotty/tools/dotc/parsing/xml/MarkupParserCommon.scala index 803470fe85a5..906d104041b2 100644 --- a/compiler/src/dotty/tools/dotc/parsing/xml/MarkupParserCommon.scala +++ b/compiler/src/dotty/tools/dotc/parsing/xml/MarkupParserCommon.scala @@ -202,6 +202,14 @@ private[dotty] trait MarkupParserCommon { /** skip optional space S? */ def xSpaceOpt(): Unit = while (isSpace(ch) && !eof) nextch() + /** skip optional space S? and return the number of consumed characters */ + def xSpaceOptN(): Int = + var i = 0 + while (isSpace(ch) && !eof) do + nextch() + i += 1 + i + /** scan [3] S ::= (#x20 | #x9 | #xD | #xA)+ */ def xSpace(): Unit = if (isSpace(ch)) { nextch(); xSpaceOpt() } diff --git a/compiler/src/dotty/tools/dotc/parsing/xml/MarkupParsers.scala b/compiler/src/dotty/tools/dotc/parsing/xml/MarkupParsers.scala index 5763e671e75a..c45a6f205f30 100644 --- a/compiler/src/dotty/tools/dotc/parsing/xml/MarkupParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/xml/MarkupParsers.scala @@ -370,10 +370,17 @@ object MarkupParsers { // parse more XML? if (charComingAfter(xSpaceOpt()) == '<') { while { - xSpaceOpt() - nextch() - ts.append(element) - charComingAfter(xSpaceOpt()) == '<' + if xSpaceOptN() == 0 then + nextch() + if content_LT(ts) then // Is `` valid xml? + xToken("/>") + charComingAfter(xSpaceOpt()) == '<' + else + // this is surely not a special node as any special node + // should start with `<{special symbol}` without space. + nextch() + ts.append(element) + charComingAfter(xSpaceOpt()) == '<' } do () handle.makeXMLseq(Span(start, curOffset, start), ts) } diff --git a/tests/pos/i16458.scala b/tests/pos/i16458.scala new file mode 100644 index 000000000000..fe4c90dcb41c --- /dev/null +++ b/tests/pos/i16458.scala @@ -0,0 +1,37 @@ +def x =
FooBar
+ +package scala.xml { + type MetaData = AnyRef + + trait NamespaceBinding + object TopScope extends NamespaceBinding + object Null + abstract class Node { + def label: String + def child: Seq[Node] + override def toString = label + child.mkString + } + class Comment(commentText: String) extends Node{ + def label = commentText + def child = Nil + } + class Elem(prefix: String, val label: String, attributes1: MetaData, scope: NamespaceBinding, minimizeEmpty: Boolean, val child: Node*) extends Node + class NodeBuffer extends Seq[Node] { + val nodes = scala.collection.mutable.ArrayBuffer.empty[Node] + def &+(o: Any): NodeBuffer = + o match { + case n: Node => nodes.addOne(n) ; this + case t: Text => nodes.addOne(Atom(t)) ; this + } + // Members declared in scala.collection.IterableOnce + def iterator: Iterator[scala.xml.Node] = nodes.iterator + // Members declared in scala.collection.SeqOps + def apply(i: Int): scala.xml.Node = nodes(i) + def length: Int = nodes.length + } + case class Text(text: String) + case class Atom(t: Text) extends Node { + def label = t.text + def child = Nil + } +} \ No newline at end of file From 4e69d79232bdf406c53561d7d50af4089ffe3f11 Mon Sep 17 00:00:00 2001 From: i10416 Date: Wed, 24 Jan 2024 12:58:26 +0900 Subject: [PATCH 02/40] improve: just use content_LT to fix 16458 Just replacing element with content_LT, it works. See https://github.com/lampepfl/dotty/pull/19522#issuecomment-1907084802 [Cherry-picked 27046feabeb0f3682c0fc734c9d4e49e7d082a0f] --- .../dotc/parsing/xml/MarkupParserCommon.scala | 8 -------- .../tools/dotc/parsing/xml/MarkupParsers.scala | 15 ++++----------- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/xml/MarkupParserCommon.scala b/compiler/src/dotty/tools/dotc/parsing/xml/MarkupParserCommon.scala index 906d104041b2..803470fe85a5 100644 --- a/compiler/src/dotty/tools/dotc/parsing/xml/MarkupParserCommon.scala +++ b/compiler/src/dotty/tools/dotc/parsing/xml/MarkupParserCommon.scala @@ -202,14 +202,6 @@ private[dotty] trait MarkupParserCommon { /** skip optional space S? */ def xSpaceOpt(): Unit = while (isSpace(ch) && !eof) nextch() - /** skip optional space S? and return the number of consumed characters */ - def xSpaceOptN(): Int = - var i = 0 - while (isSpace(ch) && !eof) do - nextch() - i += 1 - i - /** scan [3] S ::= (#x20 | #x9 | #xD | #xA)+ */ def xSpace(): Unit = if (isSpace(ch)) { nextch(); xSpaceOpt() } diff --git a/compiler/src/dotty/tools/dotc/parsing/xml/MarkupParsers.scala b/compiler/src/dotty/tools/dotc/parsing/xml/MarkupParsers.scala index c45a6f205f30..6e37acfaac84 100644 --- a/compiler/src/dotty/tools/dotc/parsing/xml/MarkupParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/xml/MarkupParsers.scala @@ -370,17 +370,10 @@ object MarkupParsers { // parse more XML? if (charComingAfter(xSpaceOpt()) == '<') { while { - if xSpaceOptN() == 0 then - nextch() - if content_LT(ts) then // Is `` valid xml? - xToken("/>") - charComingAfter(xSpaceOpt()) == '<' - else - // this is surely not a special node as any special node - // should start with `<{special symbol}` without space. - nextch() - ts.append(element) - charComingAfter(xSpaceOpt()) == '<' + xSpaceOpt() + nextch() + content_LT(ts) + charComingAfter(xSpaceOpt()) == '<' } do () handle.makeXMLseq(Span(start, curOffset, start), ts) } From 2489171c66b85a514227bdf7ccb7739d1e7f6a76 Mon Sep 17 00:00:00 2001 From: i10416 Date: Wed, 24 Jan 2024 14:21:55 +0900 Subject: [PATCH 03/40] tidy: use run test instead of compile test To check parsing properly, it is better to run a test and assert parse result. [Cherry-picked 9de9d57930b391b8f064f3630af5122d0941c949] --- tests/{pos => run}/i16458.scala | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) rename tests/{pos => run}/i16458.scala (70%) diff --git a/tests/pos/i16458.scala b/tests/run/i16458.scala similarity index 70% rename from tests/pos/i16458.scala rename to tests/run/i16458.scala index fe4c90dcb41c..1c4b0365e45e 100644 --- a/tests/pos/i16458.scala +++ b/tests/run/i16458.scala @@ -1,4 +1,21 @@ -def x =
FooBar
+ +object Test { + import scala.xml.* + def main(args: Array[String]): Unit = { + val xml =
FooBar
+ assert( + xml match + case Seq(elm: Elem, comment: Comment) if + elm.label == "div" && + elm.child(0) == Atom(Text("FooBar")) && + comment.label == " /.modal-content " + => true + case _ => false + , + xml + ) + } +} package scala.xml { type MetaData = AnyRef @@ -16,7 +33,7 @@ package scala.xml { def child = Nil } class Elem(prefix: String, val label: String, attributes1: MetaData, scope: NamespaceBinding, minimizeEmpty: Boolean, val child: Node*) extends Node - class NodeBuffer extends Seq[Node] { + class NodeBuffer extends Seq[Node] { val nodes = scala.collection.mutable.ArrayBuffer.empty[Node] def &+(o: Any): NodeBuffer = o match { From 4631efc9b8303bc70716b6018024817bf750d033 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Mon, 1 Jul 2024 22:00:16 +0200 Subject: [PATCH 04/40] Switch to Java 17 in the CI [Cherry-picked 3c453ab98a83ea5fc224245e08f6d6d0d45817c9][modified] --- .github/workflows/ci.yaml | 46 +++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1afb47457cf5..4dda716e9824 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -47,7 +47,7 @@ jobs: test_non_bootstrapped: runs-on: [self-hosted, Linux] container: - image: lampepfl/dotty:2021-03-22 + image: lampepfl/dotty:2023-11-07 options: --cpu-shares 4096 volumes: - ${{ github.workspace }}/../../cache/sbt:/root/.sbt @@ -65,8 +65,8 @@ jobs: && github.repository == 'scala/scala3' )" steps: - - name: Set JDK 16 as default - run: echo "/usr/lib/jvm/java-16-openjdk-amd64/bin" >> $GITHUB_PATH + - name: Set JDK 17 as default + run: echo "/usr/lib/jvm/java-17-openjdk-amd64/bin" >> $GITHUB_PATH ## Workaround for https://github.com/actions/runner/issues/2033 (See https://github.com/scala/scala3/pull/19720) - name: Reset existing repo @@ -99,7 +99,7 @@ jobs: test: runs-on: [self-hosted, Linux] container: - image: lampepfl/dotty:2021-03-22 + image: lampepfl/dotty:2023-11-07 options: --cpu-shares 4096 volumes: - ${{ github.workspace }}/../../cache/sbt:/root/.sbt @@ -119,8 +119,8 @@ jobs: )" steps: - - name: Set JDK 16 as default - run: echo "/usr/lib/jvm/java-16-openjdk-amd64/bin" >> $GITHUB_PATH + - name: Set JDK 17 as default + run: echo "/usr/lib/jvm/java-17-openjdk-amd64/bin" >> $GITHUB_PATH - name: Reset existing repo run: | @@ -229,7 +229,7 @@ jobs: name: MiMa runs-on: [self-hosted, Linux] container: - image: lampepfl/dotty:2021-03-22 + image: lampepfl/dotty:2023-11-07 options: --cpu-shares 4096 volumes: - ${{ github.workspace }}/../../cache/sbt:/root/.sbt @@ -248,6 +248,9 @@ jobs: && github.repository == 'scala/scala3' )" steps: + - name: Set JDK 17 as default + run: echo "/usr/lib/jvm/java-17-openjdk-amd64/bin" >> $GITHUB_PATH + - name: Reset existing repo run: | git config --global --add safe.directory /__w/scala3/scala3 @@ -272,7 +275,7 @@ jobs: community_build_a: runs-on: [self-hosted, Linux] container: - image: lampepfl/dotty:2021-03-22 + image: lampepfl/dotty:2023-11-07 options: --cpu-shares 4096 volumes: - ${{ github.workspace }}/../../cache/sbt:/root/.sbt @@ -293,6 +296,8 @@ jobs: )" steps: + - name: Set JDK 8 as default + run: echo "/usr/lib/jvm/java-8-openjdk-amd64/bin" >> $GITHUB_PATH - name: Reset existing repo run: | git config --global --add safe.directory /__w/scala3/scala3 @@ -324,7 +329,7 @@ jobs: community_build_b: runs-on: [self-hosted, Linux] container: - image: lampepfl/dotty:2021-03-22 + image: lampepfl/dotty:2023-11-07 options: --cpu-shares 4096 volumes: - ${{ github.workspace }}/../../cache/sbt:/root/.sbt @@ -345,6 +350,8 @@ jobs: )" steps: + - name: Set JDK 8 as default + run: echo "/usr/lib/jvm/java-8-openjdk-amd64/bin" >> $GITHUB_PATH - name: Reset existing repo run: | git config --global --add safe.directory /__w/scala3/scala3 @@ -376,7 +383,7 @@ jobs: community_build_c: runs-on: [self-hosted, Linux] container: - image: lampepfl/dotty:2020-11-19 + image: lampepfl/dotty:2023-11-07 options: --cpu-shares 4096 volumes: - ${{ github.workspace }}/../../cache/sbt:/root/.sbt @@ -397,6 +404,8 @@ jobs: )" steps: + - name: Set JDK 8 as default + run: echo "/usr/lib/jvm/java-8-openjdk-amd64/bin" >> $GITHUB_PATH - name: Reset existing repo run: | git config --global --add safe.directory /__w/scala3/scala3 @@ -428,7 +437,7 @@ jobs: test_sbt: runs-on: [self-hosted, Linux] container: - image: lampepfl/dotty:2021-03-22 + image: lampepfl/dotty:2023-11-07 options: --cpu-shares 4096 volumes: - ${{ github.workspace }}/../../cache/sbt:/root/.sbt @@ -447,6 +456,9 @@ jobs: )" steps: + - name: Set JDK 17 as default + run: echo "/usr/lib/jvm/java-17-openjdk-amd64/bin" >> $GITHUB_PATH + - name: Reset existing repo run: | git config --global --add safe.directory /__w/scala3/scala3 @@ -470,7 +482,7 @@ jobs: test_java8: runs-on: [self-hosted, Linux] container: - image: lampepfl/dotty:2021-03-22 + image: lampepfl/dotty:2023-11-07 options: --cpu-shares 4096 volumes: - ${{ github.workspace }}/../../cache/sbt:/root/.sbt @@ -526,7 +538,7 @@ jobs: publish_nightly: runs-on: [self-hosted, Linux] container: - image: lampepfl/dotty:2021-03-22 + image: lampepfl/dotty:2023-11-07 options: --cpu-shares 4096 volumes: - ${{ github.workspace }}/../../cache/sbt:/root/.sbt @@ -542,6 +554,8 @@ jobs: SONATYPE_USER: ${{ secrets.SONATYPE_USER_ORGSCALALANG }} steps: + - name: Set JDK 17 as default + run: echo "/usr/lib/jvm/java-17-openjdk-amd64/bin" >> $GITHUB_PATH - name: Reset existing repo run: | git config --global --add safe.directory /__w/scala3/scala3 @@ -579,7 +593,7 @@ jobs: nightly_documentation: runs-on: [self-hosted, Linux] container: - image: lampepfl/dotty:2021-03-22 + image: lampepfl/dotty:2023-11-07 options: --cpu-shares 4096 volumes: - ${{ github.workspace }}/../../cache/sbt:/root/.sbt @@ -633,7 +647,7 @@ jobs: contents: write # for actions/create-release to create a release runs-on: [self-hosted, Linux] container: - image: lampepfl/dotty:2021-03-22 + image: lampepfl/dotty:2023-11-07 options: --cpu-shares 4096 volumes: - ${{ github.workspace }}/../../cache/sbt:/root/.sbt @@ -723,7 +737,7 @@ jobs: open_issue_on_failure: runs-on: [self-hosted, Linux] container: - image: lampepfl/dotty:2021-03-22 + image: lampepfl/dotty:2023-11-07 needs: [nightly_documentation, test_windows_full] # The `failure()` expression is true iff at least one of the dependencies # of this job (including transitive dependencies) has failed. From acc8ef6742e8b2fdec11f05c46a4ffd4e8920f2d Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 14 Feb 2024 10:10:34 +0100 Subject: [PATCH 05/40] Improve -Xprint-suspension message [Cherry-picked cc698957a9f5004bd7328989fb68305e001a87e8] --- compiler/src/dotty/tools/dotc/Run.scala | 2 +- tests/neg-macros/annot-suspend-cycle.check | 2 +- tests/neg-macros/macros-in-same-project-4.check | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index 8e11c96ef363..9d668260aa27 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -141,7 +141,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint |""" val enableXprintSuspensionHint = if ctx.settings.XprintSuspension.value then "" - else "\n\nCompiling with -Xprint-suspension gives more information." + else "\n\nCompile with -Xprint-suspension for information." report.error(em"""Cyclic macro dependencies $where |Compilation stopped since no further progress can be made. | diff --git a/tests/neg-macros/annot-suspend-cycle.check b/tests/neg-macros/annot-suspend-cycle.check index e3ecea7345fd..437398f1d668 100644 --- a/tests/neg-macros/annot-suspend-cycle.check +++ b/tests/neg-macros/annot-suspend-cycle.check @@ -9,4 +9,4 @@ Compilation stopped since no further progress can be made. To fix this, place macros in one set of files and their callers in another. -Compiling with -Xprint-suspension gives more information. +Compile with -Xprint-suspension for information. diff --git a/tests/neg-macros/macros-in-same-project-4.check b/tests/neg-macros/macros-in-same-project-4.check index 79c78fd0fb7f..f027f57637f4 100644 --- a/tests/neg-macros/macros-in-same-project-4.check +++ b/tests/neg-macros/macros-in-same-project-4.check @@ -3,4 +3,4 @@ Compilation stopped since no further progress can be made. To fix this, place macros in one set of files and their callers in another. -Compiling with -Xprint-suspension gives more information. +Compile with -Xprint-suspension for information. From 5146353fa30a639004b92d43ed34feb687c05c2d Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 14 Feb 2024 10:11:15 +0100 Subject: [PATCH 06/40] Add regression test Closes #19526 [Cherry-picked 2c324360cf395c21539674445b418f056ff4cc41] --- tests/neg-macros/i19526.check | 6 ++++++ tests/neg-macros/i19526/Macro.scala | 15 +++++++++++++++ tests/neg-macros/i19526/Test.scala | 14 ++++++++++++++ tests/pos-macros/i19526b/Macro.scala | 15 +++++++++++++++ tests/pos-macros/i19526b/Test.scala | 16 ++++++++++++++++ 5 files changed, 66 insertions(+) create mode 100644 tests/neg-macros/i19526.check create mode 100644 tests/neg-macros/i19526/Macro.scala create mode 100644 tests/neg-macros/i19526/Test.scala create mode 100644 tests/pos-macros/i19526b/Macro.scala create mode 100644 tests/pos-macros/i19526b/Test.scala diff --git a/tests/neg-macros/i19526.check b/tests/neg-macros/i19526.check new file mode 100644 index 000000000000..2ad0a7aa2199 --- /dev/null +++ b/tests/neg-macros/i19526.check @@ -0,0 +1,6 @@ +Cyclic macro dependencies in tests/neg-macros/i19526/Test.scala. +Compilation stopped since no further progress can be made. + +To fix this, place macros in one set of files and their callers in another. + +Compile with -Xprint-suspension for information. diff --git a/tests/neg-macros/i19526/Macro.scala b/tests/neg-macros/i19526/Macro.scala new file mode 100644 index 000000000000..e6861c1986ef --- /dev/null +++ b/tests/neg-macros/i19526/Macro.scala @@ -0,0 +1,15 @@ +package crash.test + +import scala.language.dynamics + +import scala.quoted.* + +object Export extends Dynamic: + inline def applyDynamic(name: "apply")(inline args: Any*): Stack = ${ + applyDynamicImpl('args) + } + + def applyDynamicImpl(args: Expr[Seq[Any]])(using Quotes): Expr[Stack] = + import quotes.reflect.* + + '{ Stack("", Vector.empty) } diff --git a/tests/neg-macros/i19526/Test.scala b/tests/neg-macros/i19526/Test.scala new file mode 100644 index 000000000000..45ae51b664dd --- /dev/null +++ b/tests/neg-macros/i19526/Test.scala @@ -0,0 +1,14 @@ +package crash.test + +case class Stack private[crash] ( + exports: String, + dependsOn: Vector[Int] +) + +trait StackFactory: + val exports: Export.type = Export + + def apply(dependsOn: Int*): Stack = + Export().copy(dependsOn = dependsOn.toVector) + +// nopos-error diff --git a/tests/pos-macros/i19526b/Macro.scala b/tests/pos-macros/i19526b/Macro.scala new file mode 100644 index 000000000000..e6861c1986ef --- /dev/null +++ b/tests/pos-macros/i19526b/Macro.scala @@ -0,0 +1,15 @@ +package crash.test + +import scala.language.dynamics + +import scala.quoted.* + +object Export extends Dynamic: + inline def applyDynamic(name: "apply")(inline args: Any*): Stack = ${ + applyDynamicImpl('args) + } + + def applyDynamicImpl(args: Expr[Seq[Any]])(using Quotes): Expr[Stack] = + import quotes.reflect.* + + '{ Stack("", Vector.empty) } diff --git a/tests/pos-macros/i19526b/Test.scala b/tests/pos-macros/i19526b/Test.scala new file mode 100644 index 000000000000..1cc037298e01 --- /dev/null +++ b/tests/pos-macros/i19526b/Test.scala @@ -0,0 +1,16 @@ +package crash.test + +case class Stack private[crash] ( + exports: String, + dependsOn: Vector[Int] +) + +object Stack: + @annotation.publicInBinary + private[crash] def apply(exports: String, dependsOn: Vector[Int]): Stack = new Stack(exports, dependsOn) + +trait StackFactory: + val exports: Export.type = Export + + def apply(dependsOn: Int*): Stack = + Export().copy(dependsOn = dependsOn.toVector) From 0d70764303a34538604b472076b7c2d0e81fb858 Mon Sep 17 00:00:00 2001 From: aherlihy Date: Thu, 8 Feb 2024 16:50:08 +0100 Subject: [PATCH 07/40] update documentation for running scalajs tests with update checkfiles [Cherry-picked 1a2b7186c8d6bc11561eb6b1787288fa69ba3b91] --- docs/_docs/contributing/testing.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/_docs/contributing/testing.md b/docs/_docs/contributing/testing.md index 3c65c1591d40..bc0304616720 100644 --- a/docs/_docs/contributing/testing.md +++ b/docs/_docs/contributing/testing.md @@ -141,6 +141,11 @@ checkfiles with the test outputs. $ sbt > testCompilation --update-checkfiles ``` +Or for ScalaJS +```bash +$ sbt +> sjsCompilerTests/testOnly -- -Ddotty.tests.updateCheckfiles=TRUE +``` Use `--help` to see all the options ```bash From 46948634802ebc2b40a6157aff94b8c8e26b5185 Mon Sep 17 00:00:00 2001 From: aherlihy Date: Tue, 6 Feb 2024 18:14:23 +0100 Subject: [PATCH 08/40] Add explanation for NotClassType error message Fixes #14175 [Cherry-picked 00b6fb43bcec049f1ad89a3a8c22b9bf88c3858b] --- .../dotty/tools/dotc/reporting/messages.scala | 6 +++++- .../jsconstructorof-error-in-prepjsinterop.check | 16 ++++++++++++++++ ...jsconstructortag-error-in-prepjsinterop.check | 16 ++++++++++++++++ tests/neg/classOf.check | 6 ++++++ tests/neg/i13808.check | 4 ++++ 5 files changed, 47 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index c435ee0d7f5c..bd5efcd8ff1b 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2749,7 +2749,11 @@ extends SyntaxMsg(TargetNameOnTopLevelClassID): class NotClassType(tp: Type)(using Context) extends TypeMsg(NotClassTypeID), ShowMatchTrace(tp): def msg(using Context) = i"$tp is not a class type" - def explain(using Context) = "" + def explain(using Context) = + i"""A class type includes classes and traits in a specific order. Defining a class, even an anonymous class, + |requires specifying a linearization order for the traits it extends. For example, `A & B` is not a class type + |because it doesn't specify which trait takes precedence, A or B. For more information about class types, please see the Scala Language Specification. + |Class types also can't have refinements.""" class NotConstant(suffix: String, tp: Type)(using Context) extends TypeMsg(NotConstantID), ShowMatchTrace(tp): diff --git a/tests/neg-scalajs/jsconstructorof-error-in-prepjsinterop.check b/tests/neg-scalajs/jsconstructorof-error-in-prepjsinterop.check index 7687543ea75f..54d3d8f6c556 100644 --- a/tests/neg-scalajs/jsconstructorof-error-in-prepjsinterop.check +++ b/tests/neg-scalajs/jsconstructorof-error-in-prepjsinterop.check @@ -6,14 +6,20 @@ 14 | val b = js.constructorOf[NativeJSObject.type] // error | ^^^^^^^^^^^^^^^^^^^ | NativeJSObject.type is not a class type + | + | longer explanation available when compiling with `-explain` -- [E170] Type Error: tests/neg-scalajs/jsconstructorof-error-in-prepjsinterop.scala:16:27 ----------------------------- 16 | val c = js.constructorOf[NativeJSClass with NativeJSTrait] // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | NativeJSClass & NativeJSTrait is not a class type + | + | longer explanation available when compiling with `-explain` -- [E170] Type Error: tests/neg-scalajs/jsconstructorof-error-in-prepjsinterop.scala:17:27 ----------------------------- 17 | val d = js.constructorOf[NativeJSClass { def bar: Int }] // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | NativeJSClass{def bar: Int} is not a class type + | + | longer explanation available when compiling with `-explain` -- Error: tests/neg-scalajs/jsconstructorof-error-in-prepjsinterop.scala:19:27 ----------------------------------------- 19 | val e = js.constructorOf[JSTrait] // error | ^^^^^^^ @@ -22,19 +28,29 @@ 20 | val f = js.constructorOf[JSObject.type] // error | ^^^^^^^^^^^^^ | JSObject.type is not a class type + | + | longer explanation available when compiling with `-explain` -- [E170] Type Error: tests/neg-scalajs/jsconstructorof-error-in-prepjsinterop.scala:22:27 ----------------------------- 22 | val g = js.constructorOf[JSClass with JSTrait] // error | ^^^^^^^^^^^^^^^^^^^^ | JSClass & JSTrait is not a class type + | + | longer explanation available when compiling with `-explain` -- [E170] Type Error: tests/neg-scalajs/jsconstructorof-error-in-prepjsinterop.scala:23:27 ----------------------------- 23 | val h = js.constructorOf[JSClass { def bar: Int }] // error | ^^^^^^^^^^^^^^^^^^^^^^^^ | JSClass{def bar: Int} is not a class type + | + | longer explanation available when compiling with `-explain` -- [E170] Type Error: tests/neg-scalajs/jsconstructorof-error-in-prepjsinterop.scala:25:42 ----------------------------- 25 | def foo[A <: js.Any] = js.constructorOf[A] // error | ^ | A is not a class type + | + | longer explanation available when compiling with `-explain` -- [E170] Type Error: tests/neg-scalajs/jsconstructorof-error-in-prepjsinterop.scala:26:66 ----------------------------- 26 | def bar[A <: js.Any: scala.reflect.ClassTag] = js.constructorOf[A] // error | ^ | A is not a class type + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-scalajs/jsconstructortag-error-in-prepjsinterop.check b/tests/neg-scalajs/jsconstructortag-error-in-prepjsinterop.check index 142de318efd3..879a566d9fea 100644 --- a/tests/neg-scalajs/jsconstructortag-error-in-prepjsinterop.check +++ b/tests/neg-scalajs/jsconstructortag-error-in-prepjsinterop.check @@ -6,14 +6,20 @@ 14 | val b = js.constructorTag[NativeJSObject.type] // error | ^ | NativeJSObject.type is not a class type + | + | longer explanation available when compiling with `-explain` -- [E170] Type Error: tests/neg-scalajs/jsconstructortag-error-in-prepjsinterop.scala:16:61 ---------------------------- 16 | val c = js.constructorTag[NativeJSClass with NativeJSTrait] // error | ^ | NativeJSClass & NativeJSTrait is not a class type + | + | longer explanation available when compiling with `-explain` -- [E170] Type Error: tests/neg-scalajs/jsconstructortag-error-in-prepjsinterop.scala:17:59 ---------------------------- 17 | val d = js.constructorTag[NativeJSClass { def bar: Int }] // error | ^ | NativeJSClass{def bar: Int} is not a class type + | + | longer explanation available when compiling with `-explain` -- Error: tests/neg-scalajs/jsconstructortag-error-in-prepjsinterop.scala:19:36 ---------------------------------------- 19 | val e = js.constructorTag[JSTrait] // error | ^ @@ -22,19 +28,29 @@ 20 | val f = js.constructorTag[JSObject.type] // error | ^ | JSObject.type is not a class type + | + | longer explanation available when compiling with `-explain` -- [E170] Type Error: tests/neg-scalajs/jsconstructortag-error-in-prepjsinterop.scala:22:49 ---------------------------- 22 | val g = js.constructorTag[JSClass with JSTrait] // error | ^ | JSClass & JSTrait is not a class type + | + | longer explanation available when compiling with `-explain` -- [E170] Type Error: tests/neg-scalajs/jsconstructortag-error-in-prepjsinterop.scala:23:53 ---------------------------- 23 | val h = js.constructorTag[JSClass { def bar: Int }] // error | ^ | JSClass{def bar: Int} is not a class type + | + | longer explanation available when compiling with `-explain` -- [E170] Type Error: tests/neg-scalajs/jsconstructortag-error-in-prepjsinterop.scala:25:45 ---------------------------- 25 | def foo[A <: js.Any] = js.constructorTag[A] // error | ^ | A is not a class type + | + | longer explanation available when compiling with `-explain` -- [E170] Type Error: tests/neg-scalajs/jsconstructortag-error-in-prepjsinterop.scala:26:69 ---------------------------- 26 | def bar[A <: js.Any: scala.reflect.ClassTag] = js.constructorTag[A] // error | ^ | A is not a class type + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/classOf.check b/tests/neg/classOf.check index e3be3ca17026..e6576a60bbff 100644 --- a/tests/neg/classOf.check +++ b/tests/neg/classOf.check @@ -2,13 +2,19 @@ 6 | def f1[T] = classOf[T] // error | ^ | T is not a class type + | + | longer explanation available when compiling with `-explain` -- [E170] Type Error: tests/neg/classOf.scala:7:32 --------------------------------------------------------------------- 7 | def f2[T <: String] = classOf[T] // error | ^ | T is not a class type | | where: T is a type in method f2 with bounds <: String + | + | longer explanation available when compiling with `-explain` -- [E170] Type Error: tests/neg/classOf.scala:9:18 --------------------------------------------------------------------- 9 | val y = classOf[C { type I = String }] // error | ^^^^^^^^^^^^^^^^^^^^^ | Test.C{type I = String} is not a class type + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i13808.check b/tests/neg/i13808.check index f3e0ebac7141..e0f6d01c09db 100644 --- a/tests/neg/i13808.check +++ b/tests/neg/i13808.check @@ -2,7 +2,11 @@ 13 |case class Boom[A](value: A) derives OpaqueType, Foo // error // error | ^^^^^^^^^^ | OpaqueTypes.OpaqueType is not a class type + | + | longer explanation available when compiling with `-explain` -- [E170] Type Error: tests/neg/i13808.scala:13:49 --------------------------------------------------------------------- 13 |case class Boom[A](value: A) derives OpaqueType, Foo // error // error | ^^^ | FooModule.Foo is not a class type + | + | longer explanation available when compiling with `-explain` From beef3697c12413b9cb5d0939ce59525d41a64b1f Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 17 Feb 2024 15:56:35 +0100 Subject: [PATCH 09/40] Prefer extensions over conversions for member selection Fixes #19715 [Cherry-picked c79bd93b72e1de2eda7f794aa8ee34f26af8bf1b] --- .../dotty/tools/dotc/typer/Implicits.scala | 59 ++++++++++--------- tests/pos/i19715.scala | 15 +++++ 2 files changed, 45 insertions(+), 29 deletions(-) create mode 100644 tests/pos/i19715.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index c043492164b5..cfda041745e4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -1275,35 +1275,36 @@ trait Implicits: case alt1: SearchSuccess => var diff = compareAlternatives(alt1, alt2) assert(diff <= 0) // diff > 0 candidates should already have been eliminated in `rank` - if diff == 0 && alt1.isExtension && alt2.isExtension then - // Fall back: if both results are extension method applications, - // compare the extension methods instead of their wrappers. - def stripExtension(alt: SearchSuccess) = methPart(stripApply(alt.tree)).tpe - (stripExtension(alt1), stripExtension(alt2)) match - case (ref1: TermRef, ref2: TermRef) => - // ref1 and ref2 might refer to type variables owned by - // alt1.tstate and alt2.tstate respectively, to compare the - // alternatives correctly we need a TyperState that includes - // constraints from both sides, see - // tests/*/extension-specificity2.scala for test cases. - val constraintsIn1 = alt1.tstate.constraint ne ctx.typerState.constraint - val constraintsIn2 = alt2.tstate.constraint ne ctx.typerState.constraint - def exploreState(alt: SearchSuccess): TyperState = - alt.tstate.fresh(committable = false) - val comparisonState = - if constraintsIn1 && constraintsIn2 then - exploreState(alt1).mergeConstraintWith(alt2.tstate) - else if constraintsIn1 then - exploreState(alt1) - else if constraintsIn2 then - exploreState(alt2) - else - ctx.typerState - - diff = inContext(ctx.withTyperState(comparisonState)) { - compare(ref1, ref2) - } - case _ => + if diff == 0 && alt2.isExtension then + if alt1.isExtension then + // Fall back: if both results are extension method applications, + // compare the extension methods instead of their wrappers. + def stripExtension(alt: SearchSuccess) = methPart(stripApply(alt.tree)).tpe + (stripExtension(alt1), stripExtension(alt2)) match + case (ref1: TermRef, ref2: TermRef) => + // ref1 and ref2 might refer to type variables owned by + // alt1.tstate and alt2.tstate respectively, to compare the + // alternatives correctly we need a TyperState that includes + // constraints from both sides, see + // tests/*/extension-specificity2.scala for test cases. + val constraintsIn1 = alt1.tstate.constraint ne ctx.typerState.constraint + val constraintsIn2 = alt2.tstate.constraint ne ctx.typerState.constraint + def exploreState(alt: SearchSuccess): TyperState = + alt.tstate.fresh(committable = false) + val comparisonState = + if constraintsIn1 && constraintsIn2 then + exploreState(alt1).mergeConstraintWith(alt2.tstate) + else if constraintsIn1 then + exploreState(alt1) + else if constraintsIn2 then + exploreState(alt2) + else + ctx.typerState + + diff = inContext(ctx.withTyperState(comparisonState)): + compare(ref1, ref2) + else // alt1 is a conversion, prefer extension alt2 over it + diff = -1 if diff < 0 then alt2 else if diff > 0 then alt1 else SearchFailure(new AmbiguousImplicits(alt1, alt2, pt, argument), span) diff --git a/tests/pos/i19715.scala b/tests/pos/i19715.scala new file mode 100644 index 000000000000..91aeda5c1698 --- /dev/null +++ b/tests/pos/i19715.scala @@ -0,0 +1,15 @@ +class Tup(): + def app(n: Int): String = "a" + +class NT(t: Tup): + def toTup = t +object NT: + extension (x: NT) + def app(n: Int): Boolean = true + given Conversion[NT, Tup] = _.toTup + +def test = + val nt = new NT(Tup()) + val x = nt.app(3) + val _: Boolean = x + From 0c3717f6896e751811be7eba51c6d52cceee9b0c Mon Sep 17 00:00:00 2001 From: Hamza Remmal <56235032+hamzaremmal@users.noreply.github.com> Date: Mon, 19 Feb 2024 12:20:46 +0100 Subject: [PATCH 10/40] [regression] Publish nightly releases and releases using Java 8 (#19722) Close #19721 [test_java8] [Cherry-picked c3a1959be9d763600b9e0555239a93e5806a4637] --- .github/workflows/ci.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4dda716e9824..55aafaa8fc63 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -554,8 +554,8 @@ jobs: SONATYPE_USER: ${{ secrets.SONATYPE_USER_ORGSCALALANG }} steps: - - name: Set JDK 17 as default - run: echo "/usr/lib/jvm/java-17-openjdk-amd64/bin" >> $GITHUB_PATH + - name: Set JDK 8 as default + run: echo "/usr/lib/jvm/java-8-openjdk-amd64/bin" >> $GITHUB_PATH - name: Reset existing repo run: | git config --global --add safe.directory /__w/scala3/scala3 @@ -665,6 +665,8 @@ jobs: SONATYPE_USER: ${{ secrets.SONATYPE_USER_ORGSCALALANG }} steps: + - name: Set JDK 8 as default + run: echo "/usr/lib/jvm/java-8-openjdk-amd64/bin" >> $GITHUB_PATH - name: Reset existing repo run: | git config --global --add safe.directory /__w/scala3/scala3 From c266beede2169b89f3b311da6c8ce8ffaf70b202 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Mon, 19 Feb 2024 14:12:12 +0100 Subject: [PATCH 11/40] Add regression test for #19087 (#19726) Fixes #19087 [Cherry-picked 69170d3260a4f5989b63527192b245e4c57d8c3e] --- tests/neg/19087.check | 20 ++++++++++++++++++++ tests/neg/19087.scala | 15 +++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 tests/neg/19087.check create mode 100644 tests/neg/19087.scala diff --git a/tests/neg/19087.check b/tests/neg/19087.check new file mode 100644 index 000000000000..db6da907205d --- /dev/null +++ b/tests/neg/19087.check @@ -0,0 +1,20 @@ +-- [E103] Syntax Error: tests/neg/19087.scala:4:2 ---------------------------------------------------------------------- +4 | Option.when(state.x == 0) body // error: Illegal start of toplevel definition + | ^^^^^^ + | Illegal start of toplevel definition + | + | longer explanation available when compiling with `-explain` +-- [E040] Syntax Error: tests/neg/19087.scala:15:6 --------------------------------------------------------------------- +15 | bar = 2 // error: ',' or ')' expected + | ^^^ + | ',' or ')' expected, but identifier found +-- [E067] Syntax Error: tests/neg/19087.scala:3:4 ---------------------------------------------------------------------- +3 |def foo[T](state: State)(body: => T): Option[T] // error: only classes can have declared but undefined members + | ^ + | Declaration of method foo not allowed here: only classes can have declared but undefined members +-- [E050] Type Error: tests/neg/19087.scala:13:22 ---------------------------------------------------------------------- +13 | foo(state.copy(x = 5): // Missing ")" // error: method copy in class State does not take more parameters + | ^^^^^^^^^^^^^^^^^ + | method copy in class State does not take more parameters + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/19087.scala b/tests/neg/19087.scala new file mode 100644 index 000000000000..b52dddcad651 --- /dev/null +++ b/tests/neg/19087.scala @@ -0,0 +1,15 @@ +case class State(x: Int) + +def foo[T](state: State)(body: => T): Option[T] // error: only classes can have declared but undefined members + Option.when(state.x == 0) body // error: Illegal start of toplevel definition + +var bar = 0 +val state = State(0) + +def app: Function1[Int, Unit] = + new Function1[Int, Unit]: + def apply(x: Int): Unit = + foo(state): + foo(state.copy(x = 5): // Missing ")" // error: method copy in class State does not take more parameters + println("a") + bar = 2 // error: ',' or ')' expected From 9059b337ec4b6252d14f3b61113df5531541158a Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 17 Feb 2024 18:36:29 +0100 Subject: [PATCH 12/40] Tweak parameter accessor scheme To qualify as a super-parameter alias, a parameter of a subclass has to be passed to the primary constructor of the superclass. Fixes #19711 [Cherry-picked e6b726b04e101d69cbb1e5975fa417a8a29c43ed] --- .../tools/dotc/transform/PostTyper.scala | 3 +- tests/run/i19711.scala | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 tests/run/i19711.scala diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 01c2417b98fe..6dde9e10c833 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -112,7 +112,8 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase * This info is used in phase ParamForwarding */ private def forwardParamAccessors(impl: Template)(using Context): Unit = impl.parents match - case superCall @ Apply(fn, superArgs) :: _ if superArgs.nonEmpty => + case superCall @ Apply(fn, superArgs) :: _ + if superArgs.nonEmpty && fn.symbol.isPrimaryConstructor => fn.tpe.widen match case MethodType(superParamNames) => for case stat: ValDef <- impl.body do diff --git a/tests/run/i19711.scala b/tests/run/i19711.scala new file mode 100644 index 000000000000..a9ef03b398e2 --- /dev/null +++ b/tests/run/i19711.scala @@ -0,0 +1,29 @@ +class Foo(val s: Any): + def this(s: String) = + this(0) +class Bar(s: String) extends Foo(s): + def foo = s + +class Foo2(val s: Any) +class Bar2(s: String) extends Foo2(s): + def foo = s + +case class Config(_config: String) + +abstract class Foo3(val config: Config) { + def this(config: String) = { + this(Config(config)) + } +} + +class Bar3(config: String) extends Foo3(config) { + def foo(): Unit = { + config.getClass() + } +} + + +@main def Test = + Bar("").foo + Bar2("").foo + Bar3("").foo() From fd844b7ff9d0eb578fdde41d5c59c052438a296f Mon Sep 17 00:00:00 2001 From: Jakub Ciesluk <323892@uwr.edu.pl> Date: Fri, 16 Feb 2024 14:11:36 +0100 Subject: [PATCH 13/40] bugfix: Choose correct signature is signatureHelp for overloaded methods [Cherry-picked ab447590d61256ba98659727ab05209a67219a42] --- .../dotty/tools/dotc/reporting/messages.scala | 2 +- .../dotty/tools/dotc/util/Signatures.scala | 58 ++++++++++++++----- .../signaturehelp/SignatureHelpSuite.scala | 34 +++++++++++ 3 files changed, 80 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index bd5efcd8ff1b..ee0557ea9773 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -286,7 +286,7 @@ extends NotFoundMsg(MissingIdentID) { } } -class TypeMismatch(found: Type, expected: Type, inTree: Option[untpd.Tree], addenda: => String*)(using Context) +class TypeMismatch(val found: Type, expected: Type, inTree: Option[untpd.Tree], addenda: => String*)(using Context) extends TypeMismatchMsg(found, expected)(TypeMismatchID): def msg(using Context) = diff --git a/compiler/src/dotty/tools/dotc/util/Signatures.scala b/compiler/src/dotty/tools/dotc/util/Signatures.scala index 9131f4f761a2..0bd407261125 100644 --- a/compiler/src/dotty/tools/dotc/util/Signatures.scala +++ b/compiler/src/dotty/tools/dotc/util/Signatures.scala @@ -221,7 +221,7 @@ object Signatures { val funSymbol = fun.symbol val alternatives = if funSymbol.isLocalToBlock then List(funSymbol.denot) else funSymbol.owner.info.member(funSymbol.name).alternatives - val alternativeIndex = alternatives.map(_.symbol).indexOf(funSymbol) max 0 + val alternativeIndex = bestAlternative(alternatives, params, paramssListIndex) (alternativeIndex, alternatives) if alternativeIndex < alternatives.length then @@ -660,24 +660,56 @@ object Signatures { case msg: NoMatchingOverload => msg.alternatives case _ => Nil - val userParamsTypes = params.map(_.tpe) // Assign a score to each alternative (how many parameters are correct so far), and // use that to determine what is the current active signature. + val alternativeIndex = bestAlternative(alternatives, params, paramssIndex) + (alternativeIndex, alternatives) + } + + /** + * Given a list of alternatives, and a list of parameters, returns the index of the best + * alternative, i.e. the alternative that has the most formal parameters matching the given + * arguments and the least number of formal parameters. + * + * @param alternatives The list of alternatives to inspect. + * @param params The parameters that were given at the call site. + * @param paramssIndex Index of paramss we are currently in. + * + * @return The index of the best alternative. + */ + private def bestAlternative(alternatives: List[SingleDenotation], params: List[tpd.Tree], paramssIndex: Int)(using Context): Int = + val userParamsTypes = params.map( + _.tpe match + case e: PreviousErrorType => + /** + * In case: + * def foo(i: Int, s: String): Unit = ??? + * def foo(i: Boolean, s: Int, x: Double): Unit = ??? + * foo(false, @@) + * + * `false` has error type: `Required: Int, Found: Boolean` + */ + e.msg match + case tm: TypeMismatch => + tm.found + case _ => e + case t => t + ) val alternativesScores = alternatives.map { alt => val alreadyCurriedBonus = if (alt.symbol.paramSymss.length > paramssIndex) 1 else 0 - alt.info.stripPoly match - case tpe: MethodType => alreadyCurriedBonus + - userParamsTypes.zip(tpe.paramInfos).takeWhile{ case (t0, t1) => t0 <:< t1 }.size - case _ => 0 + alt.info.stripPoly match + case tpe: MethodType => + val score = alreadyCurriedBonus + + userParamsTypes + .zip(tpe.paramInfos) + .takeWhile { case (t0, t1) =>t0 <:< t1 } + .size + (score, -tpe.paramInfos.length) + case _ => (0, 0) } - - val bestAlternative = - if (alternativesScores.isEmpty) 0 - else alternativesScores.zipWithIndex.maxBy(_._1)._2 - - (bestAlternative, alternatives) - } + if (alternativesScores.isEmpty) 0 + else alternativesScores.zipWithIndex.maxBy(_._1)._2 } diff --git a/presentation-compiler/test/dotty/tools/pc/tests/signaturehelp/SignatureHelpSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/signaturehelp/SignatureHelpSuite.scala index 01d5e03b6c1e..ac63ef92aef5 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/signaturehelp/SignatureHelpSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/signaturehelp/SignatureHelpSuite.scala @@ -1499,3 +1499,37 @@ class SignatureHelpSuite extends BaseSignatureHelpSuite: | ^ |""".stripMargin ) + + @Test def `correct-alternative` = + check( + """ + |object x { + | def foo(i: Int, s: String): Unit = ??? + | def foo(i: Boolean, s: Int, x: Double): Unit = ??? + | + | foo(false, @@) + |} + |""".stripMargin, + """ + |foo(i: Boolean, s: Int, x: Double): Unit + | ^^^^^^ + |foo(i: Int, s: String): Unit + |""".stripMargin + ) + + @Test def `correct-alternative1` = + check( + """ + |object x { + | def foo(i: Boolean, s: String)(b: Int): Unit = ??? + | def foo(i: Boolean, s: Int)(b: String): Unit = ??? + | + | foo(false, 123)(@@) + |} + |""".stripMargin, + """ + |foo(i: Boolean, s: Int)(b: String): Unit + | ^^^^^^^^^ + |foo(i: Boolean, s: String)(b: Int): Unit + |""".stripMargin + ) From 7648f9608cb33dad130140e53b5ac5f25f316a37 Mon Sep 17 00:00:00 2001 From: Olga Mazhara Date: Sat, 17 Feb 2024 13:26:01 +0100 Subject: [PATCH 14/40] Fix #19402: improved parsing of given paramiters with absence of using keyword [Cherry-picked 300f2cc9a3ec981e00faedc7b03c7c3bcd3905f0] --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 2 +- tests/neg/i19402.scala | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 tests/neg/i19402.scala diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 98906a5703d2..9cdb0750bb94 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3958,7 +3958,7 @@ object Parsers { val tparams = typeParamClauseOpt(ParamOwner.Given) newLineOpt() val vparamss = - if in.token == LPAREN && in.lookahead.isIdent(nme.using) + if in.token == LPAREN && (in.lookahead.isIdent(nme.using) || name != EmptyTermName) then termParamClauses(ParamOwner.Given) else Nil newLinesOpt() diff --git a/tests/neg/i19402.scala b/tests/neg/i19402.scala new file mode 100644 index 000000000000..dd5a4903027c --- /dev/null +++ b/tests/neg/i19402.scala @@ -0,0 +1,11 @@ +object Test: + + class Bar(foo: Foo) + + class Foo + + given (Foo) = ??? + given (using a: Int): Int = ??? + given [T](using a: T): T = ??? + given bar(foo: Foo): Bar = Bar(foo) // error: using is expected + \ No newline at end of file From 6c8d82a966ec28cb84153476cc3b15ed8780ccec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Costa?= Date: Sun, 11 Feb 2024 18:11:07 +0000 Subject: [PATCH 15/40] Update scala-js-dom [Cherry-picked 5e0dfba9374f4d20a68fc1f849ded694bb6a5ee8] --- project/Build.scala | 4 ++-- .../content-contributors/ContentContributors.scala | 13 +++++++------ .../main/src/searchbar/SearchbarComponent.scala | 8 ++++---- .../searchbar/engine/InkuireJSSearchEngine.scala | 1 - .../src/versions-dropdown/DropdownHandler.scala | 7 ++++--- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 3c3c04408b84..50f2cc8cb6f3 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1476,7 +1476,7 @@ object Build { lazy val `scaladoc-js-common` = project.in(file("scaladoc-js/common")). enablePlugins(DottyJSPlugin). dependsOn(`scala3-library-bootstrappedJS`). - settings(libraryDependencies += ("org.scala-js" %%% "scalajs-dom" % "1.1.0").cross(CrossVersion.for3Use2_13)) + settings(libraryDependencies += ("org.scala-js" %%% "scalajs-dom" % "2.8.0")) lazy val `scaladoc-js-main` = project.in(file("scaladoc-js/main")). enablePlugins(DottyJSPlugin). @@ -1492,7 +1492,7 @@ object Build { settings( Test / fork := false, scalaJSUseMainModuleInitializer := true, - libraryDependencies += ("org.scala-js" %%% "scalajs-dom" % "1.1.0").cross(CrossVersion.for3Use2_13) + libraryDependencies += ("org.scala-js" %%% "scalajs-dom" % "2.8.0") ) def generateDocumentation(configTask: Def.Initialize[Task[GenerationConfig]]) = diff --git a/scaladoc-js/contributors/src/content-contributors/ContentContributors.scala b/scaladoc-js/contributors/src/content-contributors/ContentContributors.scala index 2e10e4fae0fa..1db7973b3129 100644 --- a/scaladoc-js/contributors/src/content-contributors/ContentContributors.scala +++ b/scaladoc-js/contributors/src/content-contributors/ContentContributors.scala @@ -5,9 +5,10 @@ import org.scalajs.dom.ext._ import scala.util.matching.Regex._ import scala.util.matching._ -import org.scalajs.dom.ext.Ajax -import scala.scalajs.js.JSON +import org.scalajs.dom._ import scala.scalajs.js +import scala.scalajs.js.JSON +import scala.scalajs.js.Thenable.Implicits.thenable2future import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future @@ -55,8 +56,8 @@ class ContentContributors: def linkForFilename(filename: String) = githubContributorsUrl() + s"/commits?path=$filename" def getAuthorsForFilename(filename: String): Future[List[FullAuthor]] = { val link = linkForFilename(filename) - Ajax.get(link).map(_.responseText).flatMap { json => - val res = JSON.parse(json).asInstanceOf[Commits] + fetch(link).flatMap(_.json()).flatMap { json => + val res = json.asInstanceOf[Commits] val authors = res.map { commit => commit.author match case null => @@ -79,8 +80,8 @@ class ContentContributors: } } def findRename(link: String, filename: String): Future[Option[String]] = { - Ajax.get(link).map(_.responseText).map { json => - val res = JSON.parse(json).asInstanceOf[CommitDescription] + fetch(link).flatMap(_.json()).map { json => + val res = json.asInstanceOf[CommitDescription] val files = res.files files .find(_.filename == filename) diff --git a/scaladoc-js/main/src/searchbar/SearchbarComponent.scala b/scaladoc-js/main/src/searchbar/SearchbarComponent.scala index 010129eb9f59..02473ecccd55 100644 --- a/scaladoc-js/main/src/searchbar/SearchbarComponent.scala +++ b/scaladoc-js/main/src/searchbar/SearchbarComponent.scala @@ -148,7 +148,7 @@ class SearchbarComponent(engine: PageSearchEngine, inkuireEngine: InkuireJSSearc val htmlEntries = results.map(result => result.pageEntry.toHTML(result.indices)) val loadMoreElement = createLoadMoreElement - def loadMoreResults(entries: List[raw.HTMLElement]): Unit = { + def loadMoreResults(entries: List[HTMLElement]): Unit = { loadMoreElement.onclick = (event: Event) => { entries.take(resultsChunkSize).foreach(_.classList.remove("hidden")) val nextElems = entries.drop(resultsChunkSize) @@ -192,7 +192,7 @@ class SearchbarComponent(engine: PageSearchEngine, inkuireEngine: InkuireJSSearc } } - def createLoadingAnimation: raw.HTMLElement = + def createLoadingAnimation: HTMLElement = div(cls := "loading-wrapper")( div(cls := "loading") ) @@ -346,7 +346,7 @@ class SearchbarComponent(engine: PageSearchEngine, inkuireEngine: InkuireJSSearc val selectedElement = resultsDiv.querySelector("[selected]") if selectedElement != null then { selectedElement.removeAttribute("selected") - def recur(elem: raw.Element): raw.Element = { + def recur(elem: Element): Element = { val prev = elem.previousElementSibling if prev == null then null else { @@ -366,7 +366,7 @@ class SearchbarComponent(engine: PageSearchEngine, inkuireEngine: InkuireJSSearc } private def handleArrowDown() = { val selectedElement = resultsDiv.querySelector("[selected]") - def recur(elem: raw.Element): raw.Element = { + def recur(elem: Element): Element = { val next = elem.nextElementSibling if next == null then null else { diff --git a/scaladoc-js/main/src/searchbar/engine/InkuireJSSearchEngine.scala b/scaladoc-js/main/src/searchbar/engine/InkuireJSSearchEngine.scala index 2dde7178e3f7..75cf6f43a907 100644 --- a/scaladoc-js/main/src/searchbar/engine/InkuireJSSearchEngine.scala +++ b/scaladoc-js/main/src/searchbar/engine/InkuireJSSearchEngine.scala @@ -2,7 +2,6 @@ package dotty.tools.scaladoc import scala.io.Source import dotty.tools.scaladoc.PageEntry -import org.scalajs.dom.webworkers.Worker import org.scalajs.dom._ import scala.scalajs.js.{ JSON, Dynamic } import scala.collection.mutable.ListBuffer diff --git a/scaladoc-js/main/src/versions-dropdown/DropdownHandler.scala b/scaladoc-js/main/src/versions-dropdown/DropdownHandler.scala index 465ee44d1ad4..3551b207a246 100644 --- a/scaladoc-js/main/src/versions-dropdown/DropdownHandler.scala +++ b/scaladoc-js/main/src/versions-dropdown/DropdownHandler.scala @@ -7,9 +7,10 @@ import scala.util.{Success,Failure} import org.scalajs.dom._ import org.scalajs.dom.ext._ import scala.scalajs.js.annotation.JSExportTopLevel -import org.scalajs.dom.ext.Ajax +import org.scalajs.dom._ import scala.scalajs.js import scala.scalajs.js.JSON +import scala.scalajs.js.Thenable.Implicits.thenable2future import utils.HTML._ @@ -33,7 +34,7 @@ class DropdownHandler: btn.classList.add("disabled") btn.classList.add("hidden") - private def getURLContent(url: String): Future[String] = Ajax.get(url).map(_.responseText) + private def getURLContent(url: String): Future[String] = fetch(url).flatMap(_.text()) window.sessionStorage.getItem(KEY) match case null => // If no key, returns null @@ -68,7 +69,7 @@ end DropdownHandler def dropdownHandler(e: Event) = e.stopPropagation() if document.getElementById("version-dropdown").getElementsByTagName("a").size > 0 && - window.getSelection.toString.length == 0 then + window.getSelection().toString.length == 0 then document.getElementById("version-dropdown").classList.toggle("expanded") document.getElementById("dropdown-trigger").classList.toggle("selected") From ed7a0ea24349770dc057dcafb69801fdc5618a4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Costa?= Date: Sun, 11 Feb 2024 18:11:34 +0000 Subject: [PATCH 16/40] Load search bar if defined in the query param [Cherry-picked e6b8e75d80597ab02ea2513ce11d37cf9c6bce01] --- .../main/src/searchbar/SearchbarComponent.scala | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/scaladoc-js/main/src/searchbar/SearchbarComponent.scala b/scaladoc-js/main/src/searchbar/SearchbarComponent.scala index 02473ecccd55..842b1cb44c23 100644 --- a/scaladoc-js/main/src/searchbar/SearchbarComponent.scala +++ b/scaladoc-js/main/src/searchbar/SearchbarComponent.scala @@ -17,6 +17,9 @@ import java.net.URI class SearchbarComponent(engine: PageSearchEngine, inkuireEngine: InkuireJSSearchEngine, parser: QueryParser): val initialChunkSize = 5 val resultsChunkSize = 20 + + val querySearch = Option(URLSearchParams(window.location.search).get("search")).filter(_.nonEmpty) + def pathToRoot() = window.document.documentElement.getAttribute("data-pathToRoot") extension (p: PageEntry) def toHTML(boldChars: Set[Int]) = @@ -262,7 +265,8 @@ class SearchbarComponent(engine: PageSearchEngine, inkuireEngine: InkuireJSSearc document.body.addEventListener("keydown", (e: KeyboardEvent) => handleGlobalKeyDown(e)) private val inputElem: html.Input = - input(cls := "scaladoc-searchbar-input", `type` := "search", `placeholder`:= "Find anything").tap { element => + val initialValue = querySearch.getOrElse("") + input(cls := "scaladoc-searchbar-input", `type` := "search", `placeholder`:= "Find anything", value := initialValue).tap { element => element.addEventListener("input", { e => clearTimeout(timeoutHandle) val inputValue = e.target.asInstanceOf[html.Input].value @@ -453,3 +457,7 @@ class SearchbarComponent(engine: PageSearchEngine, inkuireEngine: InkuireJSSearc } inputElem.dispatchEvent(new Event("input")) + if (querySearch.isDefined && !document.body.contains(rootDiv)) { + document.body.appendChild(rootDiv) + inputElem.focus() + } From 9dc1d9726426e36fec6f1fb97b23ecbb224f28e7 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Tue, 2 Jul 2024 00:37:51 +0200 Subject: [PATCH 17/40] Remove test requiring publicInBinary annotation from LTS backport --- tests/pos-macros/i19526b/Macro.scala | 15 --------------- tests/pos-macros/i19526b/Test.scala | 16 ---------------- 2 files changed, 31 deletions(-) delete mode 100644 tests/pos-macros/i19526b/Macro.scala delete mode 100644 tests/pos-macros/i19526b/Test.scala diff --git a/tests/pos-macros/i19526b/Macro.scala b/tests/pos-macros/i19526b/Macro.scala deleted file mode 100644 index e6861c1986ef..000000000000 --- a/tests/pos-macros/i19526b/Macro.scala +++ /dev/null @@ -1,15 +0,0 @@ -package crash.test - -import scala.language.dynamics - -import scala.quoted.* - -object Export extends Dynamic: - inline def applyDynamic(name: "apply")(inline args: Any*): Stack = ${ - applyDynamicImpl('args) - } - - def applyDynamicImpl(args: Expr[Seq[Any]])(using Quotes): Expr[Stack] = - import quotes.reflect.* - - '{ Stack("", Vector.empty) } diff --git a/tests/pos-macros/i19526b/Test.scala b/tests/pos-macros/i19526b/Test.scala deleted file mode 100644 index 1cc037298e01..000000000000 --- a/tests/pos-macros/i19526b/Test.scala +++ /dev/null @@ -1,16 +0,0 @@ -package crash.test - -case class Stack private[crash] ( - exports: String, - dependsOn: Vector[Int] -) - -object Stack: - @annotation.publicInBinary - private[crash] def apply(exports: String, dependsOn: Vector[Int]): Stack = new Stack(exports, dependsOn) - -trait StackFactory: - val exports: Export.type = Export - - def apply(dependsOn: Int*): Stack = - Export().copy(dependsOn = dependsOn.toVector) From 0d6dab5c1b0970f4121b42644e73c60b56caa015 Mon Sep 17 00:00:00 2001 From: Robert Stoll Date: Wed, 21 Feb 2024 17:11:34 +0100 Subject: [PATCH 18/40] remove implementation from asInstanceOf it only adds to the confusion about asInstanceOf introducing type checks. See https://contributors.scala-lang.org/t/this-trick-enables-overloading-for-opaque-types/6560/17 and https://contributors.scala-lang.org/t/pre-sip-deprecate-asinstanceof-introduce-unsafeasinstanceof/6568 [Cherry-picked 5212897ed9938ee0908c4bdb3048d5f93797233a] --- docs/_spec/12-the-scala-standard-library.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/_spec/12-the-scala-standard-library.md b/docs/_spec/12-the-scala-standard-library.md index df8626b5119c..40c136085528 100644 --- a/docs/_spec/12-the-scala-standard-library.md +++ b/docs/_spec/12-the-scala-standard-library.md @@ -68,11 +68,7 @@ abstract class Any { def isInstanceOf[a]: Boolean /** Type cast; needs to be inlined to work as given */ */ - def asInstanceOf[A]: A = this match { - case x: A => x - case _ => if (this eq null) this - else throw new ClassCastException() - } + def asInstanceOf[A]: A } /** The root class of all value types */ From ca6b2c016dcdb09a2800f74312c14e30cf88568e Mon Sep 17 00:00:00 2001 From: Jakub Ciesluk <323892@uwr.edu.pl> Date: Wed, 21 Feb 2024 17:44:38 +0100 Subject: [PATCH 19/40] improvement: Replace synthetic decorations with inlay hints Backports changes from Metals: https://github.com/scalameta/metals/pull/5983 [Cherry-picked be8abfad5eae7cdf37968cf184b4ec3a03d24777] --- ...vider.scala => PcInlayHintsProvider.scala} | 211 +++--- .../tools/pc/ScalaPresentationCompiler.scala | 10 +- .../pc/printer/ShortenedTypePrinter.scala | 2 + .../tools/pc/utils/MtagsEnrichments.scala | 18 + .../tools/pc/base/BaseInlayHintsSuite.scala | 56 ++ .../base/BaseSyntheticDecorationsSuite.scala | 60 -- .../SyntheticDecorationsSuite.scala | 528 -------------- .../SyntheticDecorationsSuite.scala | 667 ++++++++++++++++++ .../dotty/tools/pc/utils/TestInlayHints.scala | 70 ++ project/Build.scala | 2 +- 10 files changed, 932 insertions(+), 692 deletions(-) rename presentation-compiler/src/main/dotty/tools/pc/{PcSyntheticDecorationProvider.scala => PcInlayHintsProvider.scala} (63%) create mode 100644 presentation-compiler/test/dotty/tools/pc/base/BaseInlayHintsSuite.scala delete mode 100644 presentation-compiler/test/dotty/tools/pc/base/BaseSyntheticDecorationsSuite.scala delete mode 100644 presentation-compiler/test/dotty/tools/pc/tests/decorations/SyntheticDecorationsSuite.scala create mode 100644 presentation-compiler/test/dotty/tools/pc/tests/inlayHints/SyntheticDecorationsSuite.scala create mode 100644 presentation-compiler/test/dotty/tools/pc/utils/TestInlayHints.scala diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcSyntheticDecorationProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala similarity index 63% rename from presentation-compiler/src/main/dotty/tools/pc/PcSyntheticDecorationProvider.scala rename to presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala index d810ce5b07cc..3a17d30bc024 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcSyntheticDecorationProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala @@ -6,18 +6,18 @@ import java.nio.file.Paths import scala.meta.internal.metals.ReportContext import dotty.tools.pc.utils.MtagsEnrichments.* import dotty.tools.pc.printer.ShortenedTypePrinter +import scala.meta.internal.pc.InlayHints +import scala.meta.internal.pc.LabelPart +import scala.meta.internal.pc.LabelPart.* +import scala.meta.pc.InlayHintsParams import scala.meta.pc.SymbolSearch -import scala.meta.pc.SyntheticDecoration -import scala.meta.pc.SyntheticDecorationsParams -import scala.meta.internal.pc.DecorationKind -import scala.meta.internal.pc.Decoration - import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.ast.tpd.* import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.core.Flags import dotty.tools.dotc.core.StdNames.* +import dotty.tools.dotc.core.Symbols.* import dotty.tools.dotc.core.Types.* import dotty.tools.dotc.interactive.Interactive import dotty.tools.dotc.interactive.InteractiveDriver @@ -26,9 +26,13 @@ import dotty.tools.dotc.util.SourcePosition import dotty.tools.dotc.util.Spans.Span import dotty.tools.pc.IndexedContext -final class PcSyntheticDecorationsProvider( +import org.eclipse.lsp4j.InlayHint +import org.eclipse.lsp4j.InlayHintKind +import org.eclipse.{lsp4j as l} + +class PcInlayHintsProvider( driver: InteractiveDriver, - params: SyntheticDecorationsParams, + params: InlayHintsParams, symbolSearch: SymbolSearch, )(using ReportContext): @@ -43,75 +47,81 @@ final class PcSyntheticDecorationsProvider( given InferredType.Text = InferredType.Text(text) given ctx: Context = driver.currentCtx val unit = driver.currentCtx.run.nn.units.head + val pos = driver.sourcePosition(params) - def tpdTree = unit.tpdTree + def provide(): List[InlayHint] = + val deepFolder = DeepFolder[InlayHints](collectDecorations) + Interactive + .pathTo(driver.openedTrees(uri), pos)(using driver.currentCtx) + .headOption + .getOrElse(unit.tpdTree) + .enclosedChildren(pos.span) + .flatMap(tpdTree => deepFolder(InlayHints.empty, tpdTree).result()) - def provide(): List[SyntheticDecoration] = - val deepFolder = DeepFolder[Synthetics](collectDecorations) - deepFolder(Synthetics.empty, tpdTree).result() + private def adjustPos(pos: SourcePosition): SourcePosition = + pos.adjust(text)._1 def collectDecorations( - decorations: Synthetics, + inlayHints: InlayHints, tree: Tree, - ): Synthetics = + ): InlayHints = tree match - case ImplicitConversion(name, range) if params.implicitConversions() => - val adjusted = range.adjust(text)._1 - decorations + case ImplicitConversion(symbol, range) if params.implicitConversions() => + val adjusted = adjustPos(range) + inlayHints .add( - Decoration( - adjusted.startPos.toLsp, - name + "(", - DecorationKind.ImplicitConversion, - ) + adjusted.startPos.toLsp, + labelPart(symbol, symbol.decodedName) :: LabelPart("(") :: Nil, + InlayHintKind.Parameter, ) .add( - Decoration( - adjusted.endPos.toLsp, - ")", - DecorationKind.ImplicitConversion, - ) + adjusted.endPos.toLsp, + LabelPart(")") :: Nil, + InlayHintKind.Parameter, ) - case ImplicitParameters(names, pos, allImplicit) + case ImplicitParameters(symbols, pos, allImplicit) if params.implicitParameters() => + val labelParts = symbols.map(s => List(labelPart(s, s.decodedName))) val label = - if allImplicit then names.mkString("(", ", ", ")") - else names.mkString(", ", ", ", "") - decorations.add( - Decoration( - pos.adjust(text)._1.toLsp, - label, - DecorationKind.ImplicitParameter, - ) + if allImplicit then labelParts.separated("(", ", ", ")") + else labelParts.separated(", ") + inlayHints.add( + adjustPos(pos).toLsp, + label, + InlayHintKind.Parameter, + ) + case ValueOf(label, pos) if params.implicitParameters() => + inlayHints.add( + adjustPos(pos).toLsp, + LabelPart("(") :: LabelPart(label) :: List(LabelPart(")")), + InlayHintKind.Parameter, ) case TypeParameters(tpes, pos, sel) if params.typeParameters() && !syntheticTupleApply(sel) => - val label = tpes.map(toLabel(_, pos)).mkString("[", ", ", "]") - decorations.add( - Decoration( - pos.adjust(text)._1.endPos.toLsp, - label, - DecorationKind.TypeParameter, - ) + val label = tpes.map(toLabelParts(_, pos)).separated("[", ", ", "]") + inlayHints.add( + adjustPos(pos).endPos.toLsp, + label, + InlayHintKind.Type, ) - case InferredType(tpe, pos, defTree) if params.inferredTypes() => - val adjustedPos = pos.adjust(text)._1.endPos - if decorations.containsDef(adjustedPos.start) then decorations + case InferredType(tpe, pos, defTree) + if params.inferredTypes() && !isErrorTpe(tpe) => + val adjustedPos = adjustPos(pos).endPos + if inlayHints.containsDef(adjustedPos.start) then inlayHints else - decorations.add( - Decoration( + inlayHints + .add( adjustedPos.toLsp, - ": " + toLabel(tpe, pos), - DecorationKind.InferredType, - ), - adjustedPos.start, - ) - case _ => decorations + LabelPart(": ") :: toLabelParts(tpe, pos), + InlayHintKind.Type, + ) + .addDefinition(adjustedPos.start) + case _ => inlayHints - private def toLabel( + private def toLabelParts( tpe: Type, pos: SourcePosition, - ): String = + ): List[LabelPart] = val tpdPath = Interactive.pathTo(unit.tpdTree, pos.span) @@ -129,13 +139,15 @@ final class PcSyntheticDecorationsProvider( case AppliedType(tycon, args) => isInScope(tycon) && args.forall(isInScope) case _ => true - if isInScope(tpe) - then tpe + if isInScope(tpe) then tpe else tpe.metalsDealias(using indexedCtx.ctx) val dealiased = optDealias(tpe) - printer.tpe(dealiased) - end toLabel + val tpeStr = printer.tpe(dealiased) + val usedRenames = printer.getUsedRenames + val parts = partsFromType(dealiased, usedRenames) + InlayHints.makeLabelParts(parts, tpeStr) + end toLabelParts private val definitions = IndexedContext(ctx).ctx.definitions private def syntheticTupleApply(tree: Tree): Boolean = @@ -152,7 +164,32 @@ final class PcSyntheticDecorationsProvider( case _ => true else false case _ => false -end PcSyntheticDecorationsProvider + + private def labelPart(symbol: Symbol, label: String) = + if symbol.source == pos.source then + LabelPart( + label, + pos = Some(symbol.sourcePos.toLsp.getStart().nn), + ) + else + LabelPart( + label, + symbol = SemanticdbSymbols.symbolName(symbol), + ) + + private def partsFromType( + tpe: Type, + usedRenames: Map[Symbol, String], + ): List[LabelPart] = + NamedPartsAccumulator(_ => true)(Nil, tpe) + .filter(_.symbol != NoSymbol) + .map { t => + val label = usedRenames.get(t.symbol).getOrElse(t.symbol.decodedName) + labelPart(t.symbol, label) + } + + private def isErrorTpe(tpe: Type): Boolean = tpe.isError +end PcInlayHintsProvider object ImplicitConversion: def unapply(tree: Tree)(using Context) = @@ -170,7 +207,7 @@ object ImplicitConversion: val lastArgPos = args.lastOption.map(_.sourcePos).getOrElse(fun.sourcePos) Some( - fun.symbol.decodedName, + fun.symbol, lastArgPos.withStart(fun.sourcePos.start), ) end ImplicitConversion @@ -183,23 +220,29 @@ object ImplicitParameters: val (implicitArgs, providedArgs) = args.partition(isSyntheticArg) val allImplicit = providedArgs.isEmpty val pos = implicitArgs.head.sourcePos - Some(implicitArgs.map(_.symbol.decodedName), pos, allImplicit) + Some(implicitArgs.map(_.symbol), pos, allImplicit) + case _ => None + + private def isSyntheticArg(tree: Tree)(using Context) = tree match + case tree: Ident => + tree.span.isSynthetic && tree.symbol.isOneOf(Flags.GivenOrImplicit) + case _ => false +end ImplicitParameters + +object ValueOf: + def unapply(tree: Tree)(using Context) = + tree match case Apply(ta @ TypeApply(fun, _), _) if fun.span.isSynthetic && isValueOf(fun) => Some( - List("new " + tpnme.valueOf.decoded.capitalize + "(...)"), + "new " + tpnme.valueOf.decoded.capitalize + "(...)", fun.sourcePos, - true, ) case _ => None private def isValueOf(tree: Tree)(using Context) = val symbol = tree.symbol.maybeOwner symbol.name.decoded == tpnme.valueOf.decoded.capitalize - private def isSyntheticArg(tree: Tree)(using Context) = tree match - case tree: Ident => - tree.span.isSynthetic && tree.symbol.isOneOf(Flags.GivenOrImplicit) - case _ => false -end ImplicitParameters +end ValueOf object TypeParameters: def unapply(tree: Tree)(using Context) = @@ -259,35 +302,7 @@ object InferredType: */ def isValDefBind(text: Text, vd: ValDef)(using Context) = val afterDef = text.drop(vd.nameSpan.end) - val index = afterDef.indexAfterSpacesAndComments + val index = indexAfterSpacesAndComments(afterDef) index >= 0 && index < afterDef.size && afterDef(index) == '@' end InferredType - -case class Synthetics( - decorations: List[Decoration], - definitions: Set[Int], -): - def containsDef(offset: Int) = definitions(offset) - def add(decoration: Decoration, offset: Int) = - copy( - decorations = addDecoration(decoration), - definitions = definitions + offset, - ) - def add(decoration: Decoration) = - copy ( - decorations = addDecoration(decoration) - ) - - // If method has both type parameter and implicit parameter, we want the type parameter decoration to be displayed first, - // but it's added second. This method adds the decoration to the right position in the list. - private def addDecoration(decoration: Decoration): List[Decoration] = - val atSamePos = - decorations.takeWhile(_.range.getStart() == decoration.range.getStart()) - (atSamePos :+ decoration) ++ decorations.drop(atSamePos.size) - - def result(): List[Decoration] = decorations.reverse -end Synthetics - -object Synthetics: - def empty: Synthetics = Synthetics(Nil, Set.empty) diff --git a/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala b/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala index 87b67015f9e4..f3dc8d1658c4 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala @@ -106,15 +106,15 @@ case class ScalaPresentationCompiler( new PcSemanticTokensProvider(driver, params).provide().asJava } - override def syntheticDecorations( - params: SyntheticDecorationsParams - ): ju.concurrent.CompletableFuture[ju.List[SyntheticDecoration]] = + override def inlayHints( + params: InlayHintsParams + ): ju.concurrent.CompletableFuture[ju.List[l.InlayHint]] = compilerAccess.withInterruptableCompiler(Some(params))( - new ju.ArrayList[SyntheticDecoration](), + new ju.ArrayList[l.InlayHint](), params.token(), ) { access => val driver = access.compiler() - new PcSyntheticDecorationsProvider(driver, params, search) + new PcInlayHintsProvider(driver, params, search) .provide() .asJava } diff --git a/presentation-compiler/src/main/dotty/tools/pc/printer/ShortenedTypePrinter.scala b/presentation-compiler/src/main/dotty/tools/pc/printer/ShortenedTypePrinter.scala index 3b763523f9e6..ed37e133372b 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/printer/ShortenedTypePrinter.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/printer/ShortenedTypePrinter.scala @@ -66,6 +66,8 @@ class ShortenedTypePrinter( private val foundRenames = collection.mutable.LinkedHashMap.empty[Symbol, String] + def getUsedRenames: Map[Symbol, String] = foundRenames.toMap + def getUsedRenamesInfo(using Context): List[String] = foundRenames.map { (from, to) => s"type $to = ${from.showName}" diff --git a/presentation-compiler/src/main/dotty/tools/pc/utils/MtagsEnrichments.scala b/presentation-compiler/src/main/dotty/tools/pc/utils/MtagsEnrichments.scala index d6c8c10f8030..e4385392973f 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/utils/MtagsEnrichments.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/utils/MtagsEnrichments.scala @@ -316,6 +316,24 @@ object MtagsEnrichments extends CommonMtagsEnrichments: .map(source.apply) .contains('.') case _ => false + + def children(using Context): List[Tree] = + val collector = new TreeAccumulator[List[Tree]]: + def apply(x: List[Tree], tree: Tree)(using Context): List[Tree] = + tree :: x + collector + .foldOver(Nil, tree) + .reverse + + /** + * Returns the children of the tree that overlap with the given span. + */ + def enclosedChildren(span: Span)(using Context): List[Tree] = + tree.children + .filter(tree => + tree.sourcePos.exists && tree.span.start <= span.end && tree.span.end >= span.start + ) + end enclosedChildren end extension extension (imp: Import) diff --git a/presentation-compiler/test/dotty/tools/pc/base/BaseInlayHintsSuite.scala b/presentation-compiler/test/dotty/tools/pc/base/BaseInlayHintsSuite.scala new file mode 100644 index 000000000000..94b00ca82aea --- /dev/null +++ b/presentation-compiler/test/dotty/tools/pc/base/BaseInlayHintsSuite.scala @@ -0,0 +1,56 @@ +package dotty.tools.pc.base + +import java.net.URI + +import scala.meta.internal.jdk.CollectionConverters._ +import scala.meta.internal.metals.CompilerInlayHintsParams +import scala.meta.internal.metals.CompilerRangeParams +import scala.language.unsafeNulls + +import dotty.tools.pc.utils.TestInlayHints +import dotty.tools.pc.utils.TextEdits + +import org.eclipse.lsp4j.TextEdit + +class BaseInlayHintsSuite extends BasePCSuite { + + def check( + base: String, + expected: String, + kind: Option[Int] = None, + ): Unit = + def pkgWrap(text: String) = + if (text.contains("package")) text + else s"package test\n$text" + + val withPkg = pkgWrap(base) + val rangeParams = CompilerRangeParams( + URI.create("file:/InlayHints.scala"), + withPkg, + 0, + withPkg.length() + ) + val pcParams = CompilerInlayHintsParams( + rangeParams, + true, + true, + true, + true + ) + + val inlayHints = presentationCompiler + .inlayHints( + pcParams + ) + .get() + .asScala + .toList + + val obtained = TestInlayHints.applyInlayHints(withPkg, inlayHints) + + assertNoDiff( + obtained, + pkgWrap(expected) + ) + +} \ No newline at end of file diff --git a/presentation-compiler/test/dotty/tools/pc/base/BaseSyntheticDecorationsSuite.scala b/presentation-compiler/test/dotty/tools/pc/base/BaseSyntheticDecorationsSuite.scala deleted file mode 100644 index 450a42f26153..000000000000 --- a/presentation-compiler/test/dotty/tools/pc/base/BaseSyntheticDecorationsSuite.scala +++ /dev/null @@ -1,60 +0,0 @@ -package dotty.tools.pc.base - -import java.net.URI - -import scala.meta.internal.jdk.CollectionConverters._ -import scala.meta.internal.metals.CompilerSyntheticDecorationsParams -import scala.meta.internal.metals.CompilerVirtualFileParams -import scala.language.unsafeNulls - -import dotty.tools.pc.utils.TextEdits - -import org.eclipse.lsp4j.TextEdit - -class BaseSyntheticDecorationsSuite extends BasePCSuite { - - def check( - base: String, - expected: String, - kind: Option[Int] = None, - ): Unit = - def pkgWrap(text: String) = - if (text.contains("package")) text - else s"package test\n$text" - - val withPkg = pkgWrap(base) - val vFile = CompilerVirtualFileParams( - URI.create("file:/Decorations.scala"), - withPkg, - ) - - val pcParams = CompilerSyntheticDecorationsParams( - vFile, - true, - true, - true, - true, - ) - - val allDecorations = presentationCompiler - .syntheticDecorations( - pcParams - ) - .get() - .asScala - .toList - - val decorations = kind match { - case Some(k) => allDecorations.filter(_.kind == k) - case _ => allDecorations - } - - val edits = decorations.map(d => new TextEdit(d.range(), d.label())) - val obtained = TextEdits.applyEdits(withPkg, edits) - - assertNoDiff( - obtained, - pkgWrap(expected), - ) - -} \ No newline at end of file diff --git a/presentation-compiler/test/dotty/tools/pc/tests/decorations/SyntheticDecorationsSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/decorations/SyntheticDecorationsSuite.scala deleted file mode 100644 index 49ffb911aaa3..000000000000 --- a/presentation-compiler/test/dotty/tools/pc/tests/decorations/SyntheticDecorationsSuite.scala +++ /dev/null @@ -1,528 +0,0 @@ -package dotty.tools.pc.tests.decorations - -import scala.meta.internal.pc.DecorationKind - -import dotty.tools.pc.base.BaseSyntheticDecorationsSuite - -import org.junit.Test - -class SyntheticDecorationsSuite extends BaseSyntheticDecorationsSuite: - - @Test def `type-params` = - check( - """|object Main { - | def hello[T](t: T) = t - | val x = hello(List(1)) - |} - |""".stripMargin, - """|object Main { - | def hello[T](t: T) = t - | val x = hello[List[Int]](List[Int](1)) - |} - |""".stripMargin, - kind = Some(DecorationKind.TypeParameter) - ) - - @Test def `type-params2` = - check( - """|object Main { - | def hello[T](t: T) = t - | val x = hello(Map((1,"abc"))) - |} - |""".stripMargin, - """|object Main { - | def hello[T](t: T) = t - | val x = hello[Map[Int, String]](Map[Int, String]((1,"abc"))) - |} - |""".stripMargin, - kind = Some(DecorationKind.TypeParameter) - ) - - @Test def `implicit-param` = - check( - """|case class User(name: String) - |object Main { - | implicit val imp: Int = 2 - | def addOne(x: Int)(implicit one: Int) = x + one - | val x = addOne(1) - |} - |""".stripMargin, - """|case class User(name: String) - |object Main { - | implicit val imp: Int = 2 - | def addOne(x: Int)(implicit one: Int) = x + one - | val x = addOne(1)(imp) - |} - |""".stripMargin, - kind = Some(DecorationKind.ImplicitParameter) - ) - - @Test def `implicit-conversion` = - check( - """|case class User(name: String) - |object Main { - | implicit def intToUser(x: Int): User = new User(x.toString) - | val y: User = 1 - |} - |""".stripMargin, - """|case class User(name: String) - |object Main { - | implicit def intToUser(x: Int): User = new User(x.toString) - | val y: User = intToUser(1) - |} - |""".stripMargin, - kind = Some(DecorationKind.ImplicitConversion) - ) - - @Test def `using-param` = - check( - """|case class User(name: String) - |object Main { - | implicit val imp: Int = 2 - | def addOne(x: Int)(using one: Int) = x + one - | val x = addOne(1) - |} - |""".stripMargin, - """|case class User(name: String) - |object Main { - | implicit val imp: Int = 2 - | def addOne(x: Int)(using one: Int) = x + one - | val x = addOne(1)(imp) - |} - |""".stripMargin, - kind = Some(DecorationKind.ImplicitParameter) - ) - - @Test def `given-conversion` = - check( - """|case class User(name: String) - |object Main { - | given intToUser: Conversion[Int, User] = User(_.toString) - | val y: User = 1 - |} - |""".stripMargin, - """|case class User(name: String) - |object Main { - | given intToUser: Conversion[Int, User] = User(_.toString) - | val y: User = intToUser(1) - |} - |""".stripMargin, - kind = Some(DecorationKind.ImplicitConversion) - ) - - @Test def `given-conversion2` = - check( - """|trait Xg: - | def doX: Int - |trait Yg: - | def doY: String - |given (using Xg): Yg with - | def doY = "7" - |""".stripMargin, - """|trait Xg: - | def doX: Int - |trait Yg: - | def doY: String - |given (using Xg): Yg with - | def doY: String = "7" - |""".stripMargin - ) - - @Test def `basic` = - check( - """|object Main { - | val foo = 123 - |} - |""".stripMargin, - """|object Main { - | val foo: Int = 123 - |} - |""".stripMargin - ) - - @Test def `list` = - check( - """|object Main { - | val foo = List[Int](123) - |} - |""".stripMargin, - """|object Main { - | val foo: List[Int] = List[Int](123) - |} - |""".stripMargin - ) - - @Test def `list2` = - check( - """|object O { - | def m = 1 :: List(1) - |} - |""".stripMargin, - """|object O { - | def m: List[Int] = 1 ::[Int] List[Int](1) - |} - |""".stripMargin - ) - - @Test def `two-param` = - check( - """|object Main { - | val foo = Map((1, "abc")) - |} - |""".stripMargin, - """|object Main { - | val foo: Map[Int, String] = Map[Int, String]((1, "abc")) - |} - |""".stripMargin - ) - - @Test def `tuple` = - check( - """|object Main { - | val foo = (123, 456) - |} - |""".stripMargin, - """|object Main { - | val foo: (Int, Int) = (123, 456) - |} - |""".stripMargin - ) - - @Test def `import-needed` = - check( - """|object Main { - | val foo = List[String]("").toBuffer[String] - |} - |""".stripMargin, - """|object Main { - | val foo: Buffer[String] = List[String]("").toBuffer[String] - |} - |""".stripMargin - ) - - @Test def `lambda-type` = - check( - """|object Main { - | val foo = () => 123 - |} - |""".stripMargin, - """|object Main { - | val foo: () => Int = () => 123 - |} - |""".stripMargin - ) - - @Test def `block` = - check( - """|object Main { - | val foo = { val z = 123; z + 2} - |} - |""".stripMargin, - """|object Main { - | val foo: Int = { val z: Int = 123; z + 2} - |} - |""".stripMargin - ) - - @Test def `refined-types` = - check( - """|object O{ - | trait Foo { - | type T - | type G - | } - | - | val c = new Foo { type T = Int; type G = Long} - |} - |""".stripMargin, - """|object O{ - | trait Foo { - | type T - | type G - | } - | - | val c: Foo{type T = Int; type G = Long} = new Foo { type T = Int; type G = Long} - |} - |""".stripMargin - ) - - @Test def `refined-types1` = - check( - """|object O{ - | trait Foo { - | type T - | } - | val c = new Foo { type T = Int } - | val d = c - |} - |""".stripMargin, - """|object O{ - | trait Foo { - | type T - | } - | val c: Foo{type T = Int} = new Foo { type T = Int } - | val d: Foo{type T = Int} = c - |} - |""".stripMargin - ) - - @Test def `refined-types4` = - check( - """|trait Foo extends Selectable { - | type T - |} - | - |val c = new Foo { - | type T = Int - | val x = 0 - | def y = 0 - | var z = 0 - |} - |""".stripMargin, - """|trait Foo extends Selectable { - | type T - |} - | - |val c: Foo{type T = Int; val x: Int; def y: Int; val z: Int; def z_=(x$1: Int): Unit} = new Foo { - | type T = Int - | val x: Int = 0 - | def y: Int = 0 - | var z: Int = 0 - |} - |""".stripMargin - ) - - @Test def `dealias` = - check( - """|class Foo() { - | type T = Int - | def getT: T = 1 - |} - | - |object O { - | val c = new Foo().getT - |} - |""".stripMargin, - """|class Foo() { - | type T = Int - | def getT: T = 1 - |} - | - |object O { - | val c: Int = new Foo().getT - |} - |""".stripMargin - ) - - @Test def `dealias2` = - check( - """|object Foo { - | type T = Int - | def getT: T = 1 - | val c = getT - |} - |""".stripMargin, - """|object Foo { - | type T = Int - | def getT: T = 1 - | val c: T = getT - |} - |""".stripMargin - ) - - @Test def `dealias3` = - check( - """|object Foo: - | opaque type T = Int - | def getT: T = 1 - |val c = Foo.getT - |""".stripMargin, - """|object Foo: - | opaque type T = Int - | def getT: T = 1 - |val c: T = Foo.getT - |""".stripMargin - ) - - @Test def `dealias4` = - check( - """|object O: - | type M = Int - | type W = M => Int - | def get: W = ??? - | - |val m = O.get - |""".stripMargin, - """|object O: - | type M = Int - | type W = M => Int - | def get: W = ??? - | - |val m: Int => Int = O.get - |""".stripMargin - ) - - @Test def `dealias5` = - check( - """|object O: - | opaque type M = Int - | type W = M => Int - | def get: W = ??? - | - |val m = O.get - |""".stripMargin, - """|object O: - | opaque type M = Int - | type W = M => Int - | def get: W = ??? - | - |val m: M => Int = O.get - |""".stripMargin - ) - - @Test def `explicit-tuple` = - check( - """|object Main { - | val x = Tuple2.apply(1, 2) - |} - |""".stripMargin, - """|object Main { - | val x: (Int, Int) = Tuple2.apply[Int, Int](1, 2) - |} - |""".stripMargin - ) - - @Test def `explicit-tuple1` = - check( - """|object Main { - | val x = Tuple2(1, 2) - |} - |""".stripMargin, - """|object Main { - | val x: (Int, Int) = Tuple2[Int, Int](1, 2) - |} - |""".stripMargin - ) - - @Test def `tuple-unapply` = - check( - """|object Main { - | val (fst, snd) = (1, 2) - |} - |""".stripMargin, - """|object Main { - | val (fst: Int, snd: Int) = (1, 2) - |} - |""".stripMargin - ) - - @Test def `list-unapply` = - check( - """|object Main { - | val hd :: tail = List(1, 2) - |} - |""".stripMargin, - """|object Main { - | val hd: Int ::[Int] tail: List[Int] = List[Int](1, 2) - |} - |""".stripMargin - ) - - @Test def `list-match` = - check( - """|object Main { - | val x = List(1, 2) match { - | case hd :: tail => hd - | } - |} - |""".stripMargin, - """|object Main { - | val x: Int = List[Int](1, 2) match { - | case hd: Int ::[Int] tail: List[Int] => hd - | } - |} - |""".stripMargin - ) - - @Test def `case-class-unapply` = - check( - """|object Main { - |case class Foo[A](x: A, y: A) - | val Foo(fst, snd) = Foo(1, 2) - |} - |""".stripMargin, - """|object Main { - |case class Foo[A](x: A, y: A) - | val Foo[Int](fst: Int, snd: Int) = Foo[Int](1, 2) - |} - |""".stripMargin - ) - - @Test def `valueOf` = - check( - """|object O { - | def foo[Total <: Int](implicit total: ValueOf[Total]): Int = total.value - | val m = foo[500] - |} - |""".stripMargin, - """|object O { - | def foo[Total <: Int](implicit total: ValueOf[Total]): Int = total.value - | val m: Int = foo[500](new ValueOf(...)) - |} - |""".stripMargin - ) - - @Test def `case-class1` = - check( - """|object O { - |case class A(x: Int, g: Int)(implicit y: String) - |} - |""".stripMargin, - """|object O { - |case class A(x: Int, g: Int)(implicit y: String) - |} - |""".stripMargin - ) - - @Test def `ord` = - check( - """ - |object Main { - | val ordered = "acb".sorted - |} - |""".stripMargin, - """ - |object Main { - | val ordered: String = augmentString("acb").sorted[Char](Char) - |} - |""".stripMargin - ) - - @Test def `val-def-with-bind` = - check( - """ - |object O { - | val tupleBound @ (one, two) = ("1", "2") - |} - |""".stripMargin, - """ - |object O { - | val tupleBound @ (one: String, two: String) = ("1", "2") - |} - |""".stripMargin - ) - - @Test def `val-def-with-bind-and-comment` = - check( - """ - |object O { - | val tupleBound /* comment */ @ (one, two) = ("1", "2") - |} - |""".stripMargin, - """ - |object O { - | val tupleBound /* comment */ @ (one: String, two: String) = ("1", "2") - |} - |""".stripMargin - ) - diff --git a/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/SyntheticDecorationsSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/SyntheticDecorationsSuite.scala new file mode 100644 index 000000000000..0e32e5cf0682 --- /dev/null +++ b/presentation-compiler/test/dotty/tools/pc/tests/inlayHints/SyntheticDecorationsSuite.scala @@ -0,0 +1,667 @@ +package dotty.tools.pc.tests.inlayHints + +import dotty.tools.pc.base.BaseInlayHintsSuite + +import org.junit.Test +class InlayHintsSuite extends BaseInlayHintsSuite { + + @Test def `local` = + check( + """|object Main { + | def foo() = { + | implicit val imp: Int = 2 + | def addOne(x: Int)(implicit one: Int) = x + one + | val x = addOne(1) + | } + |} + |""".stripMargin, + """|object Main { + | def foo()/*: Unit<>*/ = { + | implicit val imp: Int = 2 + | def addOne(x: Int)(implicit one: Int)/*: Int<>*/ = x + one + | val x/*: Int<>*/ = addOne(1)/*(imp<<(3:17)>>)*/ + | } + |} + |""".stripMargin + ) + + @Test def `type-params` = + check( + """|object Main { + | def hello[T](t: T) = t + | val x = hello(List(1)) + |} + |""".stripMargin, + """|object Main { + | def hello[T](t: T)/*: T<<(2:12)>>*/ = t + | val x/*: List<>[Int<>]*/ = hello/*[List<>[Int<>]]*/(List/*[Int<>]*/(1)) + |} + |""".stripMargin + ) + + @Test def `type-params2` = + check( + """|object Main { + | def hello[T](t: T) = t + | val x = hello(Map((1,"abc"))) + |} + |""".stripMargin, + """|object Main { + | def hello[T](t: T)/*: T<<(2:12)>>*/ = t + | val x/*: Map<>[Int<>, String<>]*/ = hello/*[Map<>[Int<>, String<>]]*/(Map/*[Int<>, String<>]*/((1,"abc"))) + |} + |""".stripMargin, + ) + + @Test def `implicit-param` = + check( + """|case class User(name: String) + |object Main { + | implicit val imp: Int = 2 + | def addOne(x: Int)(implicit one: Int) = x + one + | val x = addOne(1) + |} + |""".stripMargin, + """|case class User(name: String) + |object Main { + | implicit val imp: Int = 2 + | def addOne(x: Int)(implicit one: Int)/*: Int<>*/ = x + one + | val x/*: Int<>*/ = addOne(1)/*(imp<<(3:15)>>)*/ + |} + |""".stripMargin + ) + + @Test def `implicit-conversion` = + check( + """|case class User(name: String) + |object Main { + | implicit def intToUser(x: Int): User = new User(x.toString) + | val y: User = 1 + |} + |""".stripMargin, + """|case class User(name: String) + |object Main { + | implicit def intToUser(x: Int): User = new User(x.toString) + | val y: User = /*intToUser<<(3:15)>>(*/1/*)*/ + |} + |""".stripMargin + ) + + @Test def `using-param` = + check( + """|case class User(name: String) + |object Main { + | implicit val imp: Int = 2 + | def addOne(x: Int)(using one: Int) = x + one + | val x = addOne(1) + |} + |""".stripMargin, + """|case class User(name: String) + |object Main { + | implicit val imp: Int = 2 + | def addOne(x: Int)(using one: Int)/*: Int<>*/ = x + one + | val x/*: Int<>*/ = addOne(1)/*(imp<<(3:15)>>)*/ + |} + |""".stripMargin + ) + + @Test def `given-conversion` = + check( + """|case class User(name: String) + |object Main { + | given intToUser: Conversion[Int, User] = User(_.toString) + | val y: User = 1 + |} + |""".stripMargin, + """|case class User(name: String) + |object Main { + | given intToUser: Conversion[Int, User] = User(_.toString) + | val y: User = /*intToUser<<(3:8)>>(*/1/*)*/ + |} + |""".stripMargin + ) + + @Test def `given-conversion2` = + check( + """|trait Xg: + | def doX: Int + |trait Yg: + | def doY: String + |given (using Xg): Yg with + | def doY = "7" + |""".stripMargin, + """|trait Xg: + | def doX: Int + |trait Yg: + | def doY: String + |given (using Xg): Yg with + | def doY/*: String<>*/ = "7" + |""".stripMargin + ) + + @Test def `basic` = + check( + """|object Main { + | val foo = 123 + |} + |""".stripMargin, + """|object Main { + | val foo/*: Int<>*/ = 123 + |} + |""".stripMargin + ) + + @Test def `list` = + check( + """|object Main { + | val foo = List[Int](123) + |} + |""".stripMargin, + """|object Main { + | val foo/*: List<>[Int<>]*/ = List[Int](123) + |} + |""".stripMargin + ) + + @Test def `list2` = + check( + """|object O { + | def m = 1 :: List(1) + |} + |""".stripMargin, + """|object O { + | def m/*: List<>[Int<>]*/ = 1 ::/*[Int<>]*/ List/*[Int<>]*/(1) + |} + |""".stripMargin + ) + + @Test def `two-param` = + check( + """|object Main { + | val foo = Map((1, "abc")) + |} + |""".stripMargin, + """|object Main { + | val foo/*: Map<>[Int<>, String<>]*/ = Map/*[Int<>, String<>]*/((1, "abc")) + |} + |""".stripMargin, + ) + + @Test def `tuple` = + check( + """|object Main { + | val foo = (123, 456) + |} + |""".stripMargin, + """|object Main { + | val foo/*: (Int<>, Int<>)*/ = (123, 456) + |} + |""".stripMargin + ) + + @Test def `import-needed` = + check( + """|object Main { + | val foo = List[String]("").toBuffer[String] + |} + |""".stripMargin, + """|object Main { + | val foo/*: Buffer<>[String<>]*/ = List[String]("").toBuffer[String] + |} + |""".stripMargin, + ) + + @Test def `lambda-type` = + check( + """|object Main { + | val foo = () => 123 + |} + |""".stripMargin, + """|object Main { + | val foo/*: () => Int<>*/ = () => 123 + |} + |""".stripMargin + ) + + @Test def `block` = + check( + """|object Main { + | val foo = { val z = 123; z + 2} + |} + |""".stripMargin, + """|object Main { + | val foo/*: Int<>*/ = { val z/*: Int<>*/ = 123; z + 2} + |} + |""".stripMargin + ) + + @Test def `refined-types` = + check( + """|object O{ + | trait Foo { + | type T + | type G + | } + | + | val c = new Foo { type T = Int; type G = Long} + |} + |""".stripMargin, + """|object O{ + | trait Foo { + | type T + | type G + | } + | + | val c/*: Foo<<(2:8)>>{type T = Int<>; type G = Long<>}*/ = new Foo { type T = Int; type G = Long} + |} + |""".stripMargin + ) + + @Test def `refined-types2` = + check( + """|object O{ + | trait Foo { + | type T + | } + | val c = new Foo { type T = Int } + | val d = c + |} + |""".stripMargin, + """|object O{ + | trait Foo { + | type T + | } + | val c/*: Foo<<(2:8)>>{type T = Int<>}*/ = new Foo { type T = Int } + | val d/*: Foo<<(2:8)>>{type T = Int<>}*/ = c + |} + |""".stripMargin + ) + + @Test def `refined-types3` = + check( + """|trait Foo extends Selectable { + | type T + |} + | + |val c = new Foo { + | type T = Int + | val x = 0 + | def y = 0 + | var z = 0 + |} + |""".stripMargin, + """|trait Foo extends Selectable { + | type T + |} + | + |val c/*: Foo<<(1:6)>>{type T = Int<>; val x: Int<>; def y: Int<>; val z: Int<>; def z_=(x$1: Int<>): Unit<>}*/ = new Foo { + | type T = Int + | val x/*: Int<>*/ = 0 + | def y/*: Int<>*/ = 0 + | var z/*: Int<>*/ = 0 + |} + |""".stripMargin + ) + + @Test def `dealias` = + check( + """|class Foo() { + | type T = Int + | def getT: T = 1 + |} + | + |object O { + | val c = new Foo().getT + |} + |""".stripMargin, + """|class Foo() { + | type T = Int + | def getT: T = 1 + |} + | + |object O { + | val c/*: Int<>*/ = new Foo().getT + |} + |""".stripMargin + ) + + @Test def `dealias2` = + check( + """|object Foo { + | type T = Int + | def getT: T = 1 + | val c = getT + |} + |""".stripMargin, + """|object Foo { + | type T = Int + | def getT: T = 1 + | val c/*: T<<(2:7)>>*/ = getT + |} + |""".stripMargin + ) + + @Test def `dealias3` = + check( + """|object Foo: + | opaque type T = Int + | def getT: T = 1 + |val c = Foo.getT + |""".stripMargin, + """|object Foo: + | opaque type T = Int + | def getT: T = 1 + |val c/*: T<<(2:14)>>*/ = Foo.getT + |""".stripMargin + ) + + @Test def `dealias4` = + check( + """|object O: + | type M = Int + | type W = M => Int + | def get: W = ??? + | + |val m = O.get + |""".stripMargin, + """|object O: + | type M = Int + | type W = M => Int + | def get: W = ??? + | + |val m/*: Int<> => Int<>*/ = O.get + |""".stripMargin + ) + + @Test def `dealias5` = + check( + """|object O: + | opaque type M = Int + | type W = M => Int + | def get: W = ??? + | + |val m = O.get + |""".stripMargin, + """|object O: + | opaque type M = Int + | type W = M => Int + | def get: W = ??? + | + |val m/*: M<<(2:13)>> => Int<>*/ = O.get + |""".stripMargin + ) + + @Test def `explicit-tuple` = + check( + """|object Main { + | val x = Tuple2.apply(1, 2) + |} + |""".stripMargin, + """|object Main { + | val x/*: (Int<>, Int<>)*/ = Tuple2.apply/*[Int<>, Int<>]*/(1, 2) + |} + |""".stripMargin + ) + + @Test def `explicit-tuple1` = + check( + """|object Main { + | val x = Tuple2(1, 2) + |} + |""".stripMargin, + """|object Main { + | val x/*: (Int<>, Int<>)*/ = Tuple2/*[Int<>, Int<>]*/(1, 2) + |} + |""".stripMargin + ) + + @Test def `tuple-unapply` = + check( + """|object Main { + | val (fst, snd) = (1, 2) + |} + |""".stripMargin, + """|object Main { + | val (fst/*: Int<>*/, snd/*: Int<>*/) = (1, 2) + |} + |""".stripMargin + ) + + @Test def `list-unapply` = + check( + """|object Main { + | val hd :: tail = List(1, 2) + |} + |""".stripMargin, + """|object Main { + | val hd/*: Int<>*/ ::/*[Int<>]*/ tail/*: List<>[Int<>]*/ = List/*[Int<>]*/(1, 2) + |} + |""".stripMargin, + ) + + @Test def `list-match` = + check( + """|object Main { + | val x = List(1, 2) match { + | case hd :: tail => hd + | } + |} + |""".stripMargin, + """|object Main { + | val x/*: Int<>*/ = List/*[Int<>]*/(1, 2) match { + | case hd/*: Int<>*/ ::/*[Int<>]*/ tail/*: List<>[Int<>]*/ => hd + | } + |} + |""".stripMargin, + ) + + @Test def `case-class-unapply` = + check( + """|object Main { + |case class Foo[A](x: A, y: A) + | val Foo(fst, snd) = Foo(1, 2) + |} + |""".stripMargin, + """|object Main { + |case class Foo[A](x: A, y: A) + | val Foo/*[Int<>]*/(fst/*: Int<>*/, snd/*: Int<>*/) = Foo/*[Int<>]*/(1, 2) + |} + |""".stripMargin, + ) + + @Test def `valueOf` = + check( + """|object O { + | def foo[Total <: Int](implicit total: ValueOf[Total]): Int = total.value + | val m = foo[500] + |} + |""".stripMargin, + """|object O { + | def foo[Total <: Int](implicit total: ValueOf[Total]): Int = total.value + | val m/*: Int<>*/ = foo[500]/*(new ValueOf(...))*/ + |} + |""".stripMargin + ) + + @Test def `case-class1` = + check( + """|object O { + |case class A(x: Int, g: Int)(implicit y: String) + |} + |""".stripMargin, + """|object O { + |case class A(x: Int, g: Int)(implicit y: String) + |} + |""".stripMargin + ) + + @Test def `ord` = + check( + """|object Main { + | val ordered = "acb".sorted + |} + |""".stripMargin, + """|object Main { + | val ordered/*: String<>*/ = /*augmentString<>(*/"acb"/*)*/.sorted/*[Char<>]*//*(Char<>)*/ + |} + |""".stripMargin + ) + + @Test def `partial-fun` = + check( + """|object Main { + | List(1).collect { case x => x } + | val x: PartialFunction[Int, Int] = { + | case 1 => 2 + | } + |} + |""".stripMargin, + """|object Main { + | List/*[Int<>]*/(1).collect/*[Int<>]*/ { case x/*: Int<>*/ => x } + | val x: PartialFunction[Int, Int] = { + | case 1 => 2 + | } + |} + |""".stripMargin + ) + + @Test def `val-def-with-bind` = + check( + """|object O { + | val tupleBound @ (one, two) = ("1", "2") + |} + |""".stripMargin, + """|object O { + | val tupleBound @ (one/*: String<>*/, two/*: String<>*/) = ("1", "2") + |} + |""".stripMargin + ) + + @Test def `val-def-with-bind-and-comment` = + check( + """|object O { + | val tupleBound /* comment */ @ (one, two) = ("1", "2") + |} + |""".stripMargin, + """|object O { + | val tupleBound /* comment */ @ (one/*: String<>*/, two/*: String<>*/) = ("1", "2") + |} + |""".stripMargin + ) + + @Test def `complex` = + check( + """|object ScalatestMock { + | class SRF + | implicit val subjectRegistrationFunction: SRF = new SRF() + | class Position + | implicit val here: Position = new Position() + | implicit class StringTestOps(name: String) { + | def should(right: => Unit)(implicit config: SRF): Unit = () + | def in(f: => Unit)(implicit pos: Position): Unit = () + | } + | implicit def instancesString: Eq[String] with Semigroup[String] = ??? + |} + | + |trait Eq[A] + |trait Semigroup[A] + | + |class DemoSpec { + | import ScalatestMock._ + | + | "foo" should { + | "checkThing1" in { + | checkThing1[String] + | } + | "checkThing2" in { + | checkThing2[String] + | } + | } + | + | "bar" should { + | "checkThing1" in { + | checkThing1[String] + | } + | } + | + | def checkThing1[A](implicit ev: Eq[A]) = ??? + | def checkThing2[A](implicit ev: Eq[A], sem: Semigroup[A]) = ??? + |} + |""".stripMargin, + """|object ScalatestMock { + | class SRF + | implicit val subjectRegistrationFunction: SRF = new SRF() + | class Position + | implicit val here: Position = new Position() + | implicit class StringTestOps(name: String) { + | def should(right: => Unit)(implicit config: SRF): Unit = () + | def in(f: => Unit)(implicit pos: Position): Unit = () + | } + | implicit def instancesString: Eq[String] with Semigroup[String] = ??? + |} + | + |trait Eq[A] + |trait Semigroup[A] + | + |class DemoSpec { + | import ScalatestMock._ + | + | /*StringTestOps<<(6:17)>>(*/"foo"/*)*/ should { + | /*StringTestOps<<(6:17)>>(*/"checkThing1"/*)*/ in { + | checkThing1[String]/*(instancesString<<(10:15)>>)*/ + | }/*(here<<(5:15)>>)*/ + | /*StringTestOps<<(6:17)>>(*/"checkThing2"/*)*/ in { + | checkThing2[String]/*(instancesString<<(10:15)>>, instancesString<<(10:15)>>)*/ + | }/*(here<<(5:15)>>)*/ + | }/*(subjectRegistrationFunction<<(3:15)>>)*/ + | + | /*StringTestOps<<(6:17)>>(*/"bar"/*)*/ should { + | /*StringTestOps<<(6:17)>>(*/"checkThing1"/*)*/ in { + | checkThing1[String]/*(instancesString<<(10:15)>>)*/ + | }/*(here<<(5:15)>>)*/ + | }/*(subjectRegistrationFunction<<(3:15)>>)*/ + | + | def checkThing1[A](implicit ev: Eq[A])/*: Nothing<>*/ = ??? + | def checkThing2[A](implicit ev: Eq[A], sem: Semigroup[A])/*: Nothing<>*/ = ??? + |} + |""".stripMargin + ) + + @Test def `import-rename` = + check( + """|import scala.collection.{AbstractMap => AB} + |import scala.collection.{Set => S} + | + |object Main { + | def test(d: S[Int], f: S[Char]): AB[Int, String] = { + | val x = d.map(_.toString) + | val y = f + | ??? + | } + | val x = test(Set(1), Set('a')) + |} + |""".stripMargin, + """|import scala.collection.{AbstractMap => AB} + |import scala.collection.{Set => S} + | + |object Main { + | def test(d: S[Int], f: S[Char]): AB[Int, String] = { + | val x/*: S<>[String<>]*/ = d.map/*[String<>]*/(_.toString) + | val y/*: S<>[Char<>]*/ = f + | ??? + | } + | val x/*: AB<>[Int<>, String<>]*/ = test(Set/*[Int<>]*/(1), Set/*[Char<>]*/('a')) + |} + |""".stripMargin, + ) + + @Test def `error-symbol` = + check( + """|package example + |case class ErrorMessage(error) + |""".stripMargin, + """|package example + |case class ErrorMessage(error) + |""".stripMargin + ) +} \ No newline at end of file diff --git a/presentation-compiler/test/dotty/tools/pc/utils/TestInlayHints.scala b/presentation-compiler/test/dotty/tools/pc/utils/TestInlayHints.scala new file mode 100644 index 000000000000..98ebb0852735 --- /dev/null +++ b/presentation-compiler/test/dotty/tools/pc/utils/TestInlayHints.scala @@ -0,0 +1,70 @@ +package dotty.tools.pc.utils + +import scala.collection.mutable.ListBuffer + +import scala.meta.internal.jdk.CollectionConverters._ +import dotty.tools.pc.utils.MtagsEnrichments.* +import dotty.tools.pc.utils.TextEdits + +import org.eclipse.lsp4j.InlayHint +import org.eclipse.lsp4j.TextEdit +import org.eclipse.{lsp4j => l} + +object TestInlayHints { + + // For not local symbols - semanticdb symbol + // val x = 123 + // | + // v + // val x<<: Int/*scala/predef/Int#*/>> = 123 + // For local symbols - definition position in source + // type T = Int + // val x: T = ??? + // val y = x + // | + // v + // val y<<: T/*(0:5,0:5)*/>> = x + def decorationString(inlayHint: InlayHint): String = { + val buffer = ListBuffer.empty[String] + + val labels = inlayHint.getLabel().nn.asScala match { + case Left(label) => List(label) + case Right(labelParts) => labelParts.asScala.map(_.getValue()).toList + } + val data = + inlayHint.getData().asInstanceOf[Array[Any]] + buffer += "/*" + labels.zip(data).foreach { case (label, data) => + buffer += label.nn + buffer ++= readData(data) + } + buffer += "*/" + buffer.toList.mkString + } + + private def readData(data: Any): List[String] = { + data match { + case data: String if data.isEmpty => Nil + case data: String => List("<<", data, ">>") + case data: l.Position => + val str = s"(${data.getLine()}:${data.getCharacter()})" + List("<<", str, ">>") + } + } + + def applyInlayHints(text: String, inlayHints: List[InlayHint]): String = { + val textEdits = inlayHints.map { hint => + val newText = decorationString(hint) + val range = new l.Range(hint.getPosition(), hint.getPosition()) + new TextEdit( + range, + newText + ) + } + TextEdits.applyEdits(text, textEdits) + } + + def removeInlayHints(text: String): String = + text.replaceAll(raw"\/\*(.*?)\*\/", "").nn + +} \ No newline at end of file diff --git a/project/Build.scala b/project/Build.scala index 50f2cc8cb6f3..59321b5f677d 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1128,7 +1128,7 @@ object Build { BuildInfoPlugin.buildInfoDefaultSettings lazy val presentationCompilerSettings = { - val mtagsVersion = "1.2.0+67-30f8ab53-SNAPSHOT" + val mtagsVersion = "1.2.2+25-bb9dfbb9-SNAPSHOT" Seq( resolvers ++= Resolver.sonatypeOssRepos("snapshots"), From b09d745e32b7af4742f6289a9af1afab1e2461b3 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 14 Feb 2024 17:30:25 +0100 Subject: [PATCH 20/40] reproduce the issue [Cherry-picked 3e2a88e7288437a32a1595d1c0a2732002007530] --- tests/run/i19619/InnerClass.java | 67 ++++++++++++++++++++++++ tests/run/i19619/InnerClassGen.java | 80 +++++++++++++++++++++++++++++ tests/run/i19619/RawTypes.java | 18 +++++++ tests/run/i19619/Test.scala | 42 +++++++++++++++ 4 files changed, 207 insertions(+) create mode 100644 tests/run/i19619/InnerClass.java create mode 100644 tests/run/i19619/InnerClassGen.java create mode 100644 tests/run/i19619/RawTypes.java create mode 100644 tests/run/i19619/Test.scala diff --git a/tests/run/i19619/InnerClass.java b/tests/run/i19619/InnerClass.java new file mode 100644 index 000000000000..32bb1642a232 --- /dev/null +++ b/tests/run/i19619/InnerClass.java @@ -0,0 +1,67 @@ +// InnerClass.java + +package lib; + +public class InnerClass { + + public class Inner { + public U innerField; + + public Inner(U innerField) { + this.innerField = innerField; + } + + public U getInnerField() { + return innerField; + } + } + + public class Outer { + + public class Nested { + + public U outerField; + public V innerField; + + public Nested(U outerField, V innerField) { + this.outerField = outerField; + this.innerField = innerField; + } + + public U getOuterField() { + return outerField; + } + + public V getInnerField() { + return innerField; + } + } + } + + public Inner createInner(U innerField) { + return new Inner<>(innerField); + } + + public Outer.Nested createNested(U outerField, V innerField) { + Outer outer = new Outer<>(); + return outer.new Nested<>(outerField, innerField); + } + + public static InnerClass.Inner createInnerStatic(U innerField) { + InnerClass innerClass = new InnerClass(); + return innerClass.new Inner<>(innerField); + } + + public static InnerClass.Outer.Nested createNestedStatic(U outerField, V innerField) { + InnerClass innerClass = new InnerClass(); + InnerClass.Outer outer = innerClass.new Outer<>(); + return outer.new Nested<>(outerField, innerField); + } + + public static void consumeNestedStatic(InnerClass.Outer.Nested nested) { + } + + public static void consumeNestedStatic2(Outer.Nested nested) { + } + +} diff --git a/tests/run/i19619/InnerClassGen.java b/tests/run/i19619/InnerClassGen.java new file mode 100644 index 000000000000..3a691aa0608f --- /dev/null +++ b/tests/run/i19619/InnerClassGen.java @@ -0,0 +1,80 @@ +// InnerClassGen.java + +package lib; + +public class InnerClassGen { + + public class Inner { + public T rootField; + public U innerField; + + public Inner(T rootField, U innerField) { + this.rootField = rootField; + this.innerField = innerField; + } + + public T getRootField() { + return rootField; + } + + public U getInnerField() { + return innerField; + } + } + + public class Outer { + + public class Nested { + public T rootField; + public U outerField; + public V innerField; + + public Nested(T rootField, U outerField, V innerField) { + this.rootField = rootField; + this.outerField = outerField; + this.innerField = innerField; + } + + public T getRootField() { + return rootField; + } + + public U getOuterField() { + return outerField; + } + + public V getInnerField() { + return innerField; + } + } + } + + public static class OuterStatic { + public static class NestedStatic { + } + } + + public Inner createInner(T rootField, U innerField) { + return new Inner<>(rootField, innerField); + } + + public Outer.Nested createNested(T rootField, U outerField, V innerField) { + Outer outer = new Outer<>(); + return outer.new Nested<>(rootField, outerField, innerField); + } + + public static InnerClassGen.Inner createInnerStatic(T rootField, U innerField) { + InnerClassGen innerClassGen = new InnerClassGen<>(); + return innerClassGen.new Inner<>(rootField, innerField); + } + + public static InnerClassGen.Outer.Nested createNestedStatic(T rootField, U outerField, V innerField) { + InnerClassGen innerClassGen = new InnerClassGen<>(); + InnerClassGen.Outer outer = innerClassGen.new Outer<>(); + return outer.new Nested<>(rootField, outerField, innerField); + } + + public static void consumeNestedStatic(InnerClassGen.Outer.Nested nested) { + } + +} diff --git a/tests/run/i19619/RawTypes.java b/tests/run/i19619/RawTypes.java new file mode 100644 index 000000000000..4373a04093eb --- /dev/null +++ b/tests/run/i19619/RawTypes.java @@ -0,0 +1,18 @@ +// RawTypes.java + +package lib; + +public class RawTypes { + + public class C { + public class D { + } + } + + public static void mii_Raw_Raw(RawTypes.C.D d) { + } + + public static void mii_Raw_Raw2(C.D d) { + } + +} diff --git a/tests/run/i19619/Test.scala b/tests/run/i19619/Test.scala new file mode 100644 index 000000000000..7e10950c6cfd --- /dev/null +++ b/tests/run/i19619/Test.scala @@ -0,0 +1,42 @@ +import lib.InnerClass +import lib.InnerClassGen +import lib.RawTypes + +@main def Test = + + locally: + val ici: InnerClass = new InnerClass() + val ici_inner1: ici.Inner[Long] = ici.createInner[Long](47L) // ok, but should error + val ici_inner2: InnerClass#Inner[Long] = ici.createInner[Long](47L) + val ici_inner3: InnerClass#Inner[Long] = InnerClass.createInnerStatic[Long](47L) + + val ici_outer: InnerClass#Outer[Long] = new ici.Outer[Long]() + val ici_nested1: InnerClass#Outer[Long]#Nested[Int] = new ici_outer.Nested[Int](47L, 23) + val ici_nested2: InnerClass#Outer[Long]#Nested[Int] = ici.createNested[Long, Int](47L, 23) + val ici_nested3: InnerClass#Outer[Long]#Nested[Int] = InnerClass.createNestedStatic[Long, Int](47L, 23) + + InnerClass.consumeNestedStatic(ici_nested3) + InnerClass.consumeNestedStatic2(ici_nested3) // error: dotty is inferring static selection when there is no explicit prefix + + locally: + val ici: InnerClassGen[String] = new InnerClassGen() + val ici_inner1: ici.Inner[Long] = ici.createInner[Long]("Hello", 47L) // ok, but should error + val ici_inner2: InnerClassGen[String]#Inner[Long] = ici.createInner[Long]("Hello", 47L) + val ici_inner3: InnerClassGen[String]#Inner[Long] = InnerClassGen.createInnerStatic[String, Long]("Hello", 47L) + + val ici_outer: InnerClassGen[String]#Outer[Long] = new ici.Outer[Long]() + val ici_nested1: InnerClassGen[String]#Outer[Long]#Nested[Int] = new ici_outer.Nested[Int]("Hello", 47L, 23) + val ici_nested2: InnerClassGen[String]#Outer[Long]#Nested[Int] = ici.createNested[Long, Int]("Hello", 47L, 23) + val ici_nested3: InnerClassGen[String]#Outer[Long]#Nested[Int] = InnerClassGen.createNestedStatic[String, Long, Int]("Hello", 47L, 23) + + InnerClassGen.consumeNestedStatic(ici_nested3) + + locally: + val rt: RawTypes = new RawTypes() + val c: RawTypes#C[String] = new rt.C[String]() + + val cd_ii: RawTypes#C[String]#D[String] = new c.D[String]() + val cd_ii_Raw: RawTypes#C[?]#D[?] = cd_ii + + RawTypes.mii_Raw_Raw(cd_ii_Raw) + RawTypes.mii_Raw_Raw2(cd_ii_Raw) // error: dotty is inferring static selection when there is no explicit prefix From 739cebb39261a6ce911f20eb34cc7c940b5687f5 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Fri, 16 Feb 2024 17:36:50 +0100 Subject: [PATCH 21/40] do not look in companion in javaFindMember in typedSelect [Cherry-picked b3501a67402e80cac5a87b7cb90d3d93625ae1db] --- .../src/dotty/tools/dotc/core/ContextOps.scala | 15 ++++++++++++--- .../src/dotty/tools/dotc/typer/TypeAssigner.scala | 5 ++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/ContextOps.scala b/compiler/src/dotty/tools/dotc/core/ContextOps.scala index 920da377f9b4..55fb31fd1916 100644 --- a/compiler/src/dotty/tools/dotc/core/ContextOps.scala +++ b/compiler/src/dotty/tools/dotc/core/ContextOps.scala @@ -34,7 +34,10 @@ object ContextOps: if (elem.name == name) return elem.sym.denot // return self } val pre = ctx.owner.thisType - if ctx.isJava then javaFindMember(name, pre, required, excluded) + if ctx.isJava then + // Note: I didn't verify if there exists a code path that would require `lookInCompanion = true`, + // it is just to preserve the original behavior. + javaFindMember(name, pre, lookInCompanion = true, required, excluded) else pre.findMember(name, pre, required, excluded) } else // we are in the outermost context belonging to a class; self is invisible here. See inClassContext. @@ -43,7 +46,13 @@ object ContextOps: ctx.scope.denotsNamed(name).filterWithFlags(required, excluded).toDenot(NoPrefix) } - final def javaFindMember(name: Name, pre: Type, required: FlagSet = EmptyFlags, excluded: FlagSet = EmptyFlags): Denotation = + /** Look in the prefix with Java semantics. + * @param lookInCompanion If true, try in the companion class of a module as a fallback. + * Note: originally this was used to type Select nodes in Java code, + * but that is no longer the case. + * It is preserved in case it is necessary for denotNamed, but this is unverified. + */ + final def javaFindMember(name: Name, pre: Type, lookInCompanion: Boolean, required: FlagSet = EmptyFlags, excluded: FlagSet = EmptyFlags): Denotation = assert(ctx.isJava) inContext(ctx) { @@ -53,7 +62,7 @@ object ContextOps: val directSearch = pre.findMember(name, pre, required, excluded) // 2. Try to search in companion class if current is an object. - def searchCompanionClass = if preSym.is(Flags.Module) then + def searchCompanionClass = if lookInCompanion && preSym.is(Flags.Module) then preSym.companionClass.thisType.findMember(name, pre, required, excluded) else NoDenotation diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 0896d78c7229..02d8c2f51bf4 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -150,7 +150,10 @@ trait TypeAssigner { val pre = maybeSkolemizePrefix(qualType, name) val mbr = if ctx.isJava then - ctx.javaFindMember(name, pre) + // don't look in the companion class here if qual is a module, + // we use backtracking to instead change the qual to the companion class + // if this fails. + ctx.javaFindMember(name, pre, lookInCompanion = false) else qualType.findMember(name, pre) From cda3388eda093c1e2d23a6f04aa9e670ea943cfb Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Fri, 16 Feb 2024 19:11:21 +0100 Subject: [PATCH 22/40] Widen prefix in findRef of class from Java sources. potential TODO: tree's of Raw types from Java are still incorrect once they are serialized to TASTy, however it is debateable if we care to support them. [Cherry-picked 3ab29995e1571c6f19dea41d47b143f4895a8cac] --- .../tools/dotc/core/SymDenotations.scala | 7 ++ .../src/dotty/tools/dotc/typer/Typer.scala | 4 + .../Yjava-tasty-paths/a/InnerClass.java | 65 +++++++++++++++ .../Yjava-tasty-paths/a/InnerClassGen.java | 78 ++++++++++++++++++ .../Yjava-tasty-paths/a/RawTypes.java | 16 ++++ .../Yjava-tasty-paths/a/package.scala | 2 + .../pipelining/Yjava-tasty-paths/b/Test.scala | 50 ++++++++++++ .../pipelining/Yjava-tasty-paths/build.sbt | 31 +++++++ .../project/DottyInjectedPlugin.scala | 12 +++ sbt-test/pipelining/Yjava-tasty-paths/test | 5 ++ tests/neg/i19619/InnerClass.java | 67 ++++++++++++++++ tests/neg/i19619/InnerClassGen.java | 80 +++++++++++++++++++ tests/neg/i19619/Test.scala | 12 +++ tests/run/i19619/Test.scala | 8 +- 14 files changed, 433 insertions(+), 4 deletions(-) create mode 100644 sbt-test/pipelining/Yjava-tasty-paths/a/InnerClass.java create mode 100644 sbt-test/pipelining/Yjava-tasty-paths/a/InnerClassGen.java create mode 100644 sbt-test/pipelining/Yjava-tasty-paths/a/RawTypes.java create mode 100644 sbt-test/pipelining/Yjava-tasty-paths/a/package.scala create mode 100644 sbt-test/pipelining/Yjava-tasty-paths/b/Test.scala create mode 100644 sbt-test/pipelining/Yjava-tasty-paths/build.sbt create mode 100644 sbt-test/pipelining/Yjava-tasty-paths/project/DottyInjectedPlugin.scala create mode 100644 sbt-test/pipelining/Yjava-tasty-paths/test create mode 100644 tests/neg/i19619/InnerClass.java create mode 100644 tests/neg/i19619/InnerClassGen.java create mode 100644 tests/neg/i19619/Test.scala diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index a8a1be309b0c..d18f88dabfd8 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1501,6 +1501,13 @@ object SymDenotations { def namedType(using Context): NamedType = if (isType) typeRef else termRef + /** Like typeRef, but the prefix is widened. + * + * See tests/neg/i19619/Test.scala + */ + def javaTypeRef(using Context) = + TypeRef(maybeOwner.reachablePrefix.widen, symbol) + /** Like typeRef, but objects in the prefix are represented by their singleton type, * this means we output `pre.O.member` rather than `pre.O$.this.member`. * diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 55adda0505ae..f0be4ac75b9f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -453,6 +453,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if (isSelfDenot(defDenot)) curOwner.enclosingClass.thisType else if (ctx.isJava && defDenot.symbol.isStatic) { defDenot.symbol.namedType + } + else if (ctx.isJava && defDenot.symbol.isClass) { + // in a java context a raw identifier to a class should have a widened prefix. + defDenot.symbol.javaTypeRef } else { val effectiveOwner = if (curOwner.isTerm && defDenot.symbol.maybeOwner.isType) diff --git a/sbt-test/pipelining/Yjava-tasty-paths/a/InnerClass.java b/sbt-test/pipelining/Yjava-tasty-paths/a/InnerClass.java new file mode 100644 index 000000000000..c23bc54397dd --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-paths/a/InnerClass.java @@ -0,0 +1,65 @@ +package a; + +public class InnerClass { + + public class Inner { + public U innerField; + + public Inner(U innerField) { + this.innerField = innerField; + } + + public U getInnerField() { + return innerField; + } + } + + public class Outer { + + public class Nested { + + public U outerField; + public V innerField; + + public Nested(U outerField, V innerField) { + this.outerField = outerField; + this.innerField = innerField; + } + + public U getOuterField() { + return outerField; + } + + public V getInnerField() { + return innerField; + } + } + } + + public Inner createInner(U innerField) { + return new Inner<>(innerField); + } + + public Outer.Nested createNested(U outerField, V innerField) { + Outer outer = new Outer<>(); + return outer.new Nested<>(outerField, innerField); + } + + public static InnerClass.Inner createInnerStatic(U innerField) { + InnerClass innerClass = new InnerClass(); + return innerClass.new Inner<>(innerField); + } + + public static InnerClass.Outer.Nested createNestedStatic(U outerField, V innerField) { + InnerClass innerClass = new InnerClass(); + InnerClass.Outer outer = innerClass.new Outer<>(); + return outer.new Nested<>(outerField, innerField); + } + + public static void consumeNestedStatic(InnerClass.Outer.Nested nested) { + } + + public static void consumeNestedStatic2(Outer.Nested nested) { + } + +} diff --git a/sbt-test/pipelining/Yjava-tasty-paths/a/InnerClassGen.java b/sbt-test/pipelining/Yjava-tasty-paths/a/InnerClassGen.java new file mode 100644 index 000000000000..4011cf2fc244 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-paths/a/InnerClassGen.java @@ -0,0 +1,78 @@ +package a; + +public class InnerClassGen { + + public class Inner { + public T rootField; + public U innerField; + + public Inner(T rootField, U innerField) { + this.rootField = rootField; + this.innerField = innerField; + } + + public T getRootField() { + return rootField; + } + + public U getInnerField() { + return innerField; + } + } + + public class Outer { + + public class Nested { + public T rootField; + public U outerField; + public V innerField; + + public Nested(T rootField, U outerField, V innerField) { + this.rootField = rootField; + this.outerField = outerField; + this.innerField = innerField; + } + + public T getRootField() { + return rootField; + } + + public U getOuterField() { + return outerField; + } + + public V getInnerField() { + return innerField; + } + } + } + + public static class OuterStatic { + public static class NestedStatic { + } + } + + public Inner createInner(T rootField, U innerField) { + return new Inner<>(rootField, innerField); + } + + public Outer.Nested createNested(T rootField, U outerField, V innerField) { + Outer outer = new Outer<>(); + return outer.new Nested<>(rootField, outerField, innerField); + } + + public static InnerClassGen.Inner createInnerStatic(T rootField, U innerField) { + InnerClassGen innerClassGen = new InnerClassGen<>(); + return innerClassGen.new Inner<>(rootField, innerField); + } + + public static InnerClassGen.Outer.Nested createNestedStatic(T rootField, U outerField, V innerField) { + InnerClassGen innerClassGen = new InnerClassGen<>(); + InnerClassGen.Outer outer = innerClassGen.new Outer<>(); + return outer.new Nested<>(rootField, outerField, innerField); + } + + public static void consumeNestedStatic(InnerClassGen.Outer.Nested nested) { + } + +} diff --git a/sbt-test/pipelining/Yjava-tasty-paths/a/RawTypes.java b/sbt-test/pipelining/Yjava-tasty-paths/a/RawTypes.java new file mode 100644 index 000000000000..4f97c45e7f90 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-paths/a/RawTypes.java @@ -0,0 +1,16 @@ +package a; + +public class RawTypes { + + public class C { + public class D { + } + } + + public static void mii_Raw_Raw(RawTypes.C.D d) { + } + + public static void mii_Raw_Raw2(C.D d) { + } + +} diff --git a/sbt-test/pipelining/Yjava-tasty-paths/a/package.scala b/sbt-test/pipelining/Yjava-tasty-paths/a/package.scala new file mode 100644 index 000000000000..93f99e9892fe --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-paths/a/package.scala @@ -0,0 +1,2 @@ +// THIS FILE EXISTS SO THAT `A.java` WILL BE COMPILED BY SCALAC +package a diff --git a/sbt-test/pipelining/Yjava-tasty-paths/b/Test.scala b/sbt-test/pipelining/Yjava-tasty-paths/b/Test.scala new file mode 100644 index 000000000000..4f555d3b8ac9 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-paths/b/Test.scala @@ -0,0 +1,50 @@ +package b + +import a.InnerClass +import a.InnerClassGen +import a.RawTypes + +object B { + @main def test = { + locally { + val ici: InnerClass = new InnerClass() + // val ici_inner1: ici.Inner[Long] = ici.createInner[Long](47L) // error + val ici_inner2: InnerClass#Inner[Long] = ici.createInner[Long](47L) + val ici_inner3: InnerClass#Inner[Long] = InnerClass.createInnerStatic[Long](47L) + + val ici_outer: InnerClass#Outer[Long] = new ici.Outer[Long]() + val ici_nested1: InnerClass#Outer[Long]#Nested[Int] = new ici_outer.Nested[Int](47L, 23) + val ici_nested2: InnerClass#Outer[Long]#Nested[Int] = ici.createNested[Long, Int](47L, 23) + val ici_nested3: InnerClass#Outer[Long]#Nested[Int] = InnerClass.createNestedStatic[Long, Int](47L, 23) + + InnerClass.consumeNestedStatic(ici_nested3) + InnerClass.consumeNestedStatic2(ici_nested3) + } + + locally { + val ici: InnerClassGen[String] = new InnerClassGen() + // val ici_inner1: ici.Inner[Long] = ici.createInner[Long]("Hello", 47L) // error + val ici_inner2: InnerClassGen[String]#Inner[Long] = ici.createInner[Long]("Hello", 47L) + val ici_inner3: InnerClassGen[String]#Inner[Long] = InnerClassGen.createInnerStatic[String, Long]("Hello", 47L) + + val ici_outer: InnerClassGen[String]#Outer[Long] = new ici.Outer[Long]() + val ici_nested1: InnerClassGen[String]#Outer[Long]#Nested[Int] = new ici_outer.Nested[Int]("Hello", 47L, 23) + val ici_nested2: InnerClassGen[String]#Outer[Long]#Nested[Int] = ici.createNested[Long, Int]("Hello", 47L, 23) + val ici_nested3: InnerClassGen[String]#Outer[Long]#Nested[Int] = InnerClassGen.createNestedStatic[String, Long, Int]("Hello", 47L, 23) + + InnerClassGen.consumeNestedStatic(ici_nested3) + } + + locally { + val rt: RawTypes = new RawTypes() + val c: RawTypes#C[String] = new rt.C[String]() + + val cd_ii: RawTypes#C[String]#D[String] = new c.D[String]() + val cd_ii_Raw: RawTypes#C[?]#D[?] = cd_ii + + RawTypes.mii_Raw_Raw(cd_ii_Raw) + // RawTypes.mii_Raw_Raw2(cd_ii_Raw) // error: dotty still doesnt rewrite the tree of a raw type to a type with wildcards + } + } + +} diff --git a/sbt-test/pipelining/Yjava-tasty-paths/build.sbt b/sbt-test/pipelining/Yjava-tasty-paths/build.sbt new file mode 100644 index 000000000000..d63d1f9a3f7e --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-paths/build.sbt @@ -0,0 +1,31 @@ +lazy val a = project.in(file("a")) + .settings( + scalacOptions += "-Yjava-tasty", // enable pickling of java signatures + scalacOptions ++= Seq("-Yjava-tasty-output", ((ThisBuild / baseDirectory).value / "a-paths-java-tasty.jar").toString), + scalacOptions += "-Ycheck:all", + Compile / classDirectory := ((ThisBuild / baseDirectory).value / "a-paths-classes"), // send classfiles to a different directory + ) + +lazy val b = project.in(file("b")) + .settings( + Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-paths-java-tasty.jar")), + scalacOptions += "-Ycheck:all", + scalacOptions += "-explain-cyclic", + scalacOptions += "-Ydebug-cyclic", + ) + .settings( + fork := true, // we have to fork the JVM if we actually want to run the code with correct failure semantics + Runtime / unmanagedClasspath += Attributed.blank((ThisBuild / baseDirectory).value / "a-paths-classes"), // make sure the java classes are visible at runtime + ) + +// same as b, but adds the real classes to the classpath instead of the tasty jar +lazy val bAlt = project.in(file("b-alt")) + .settings( + Compile / sources := (b / Compile / sources).value, + Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-paths-classes")), + scalacOptions += "-Ycheck:all", + ) + .settings( + fork := true, // we have to fork the JVM if we actually want to run the code with correct failure semantics + Runtime / unmanagedClasspath += Attributed.blank((ThisBuild / baseDirectory).value / "a-paths-classes"), // make sure the java classes are visible at runtime + ) diff --git a/sbt-test/pipelining/Yjava-tasty-paths/project/DottyInjectedPlugin.scala b/sbt-test/pipelining/Yjava-tasty-paths/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..69f15d168bfc --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-paths/project/DottyInjectedPlugin.scala @@ -0,0 +1,12 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion"), + scalacOptions += "-source:3.0-migration" + ) +} diff --git a/sbt-test/pipelining/Yjava-tasty-paths/test b/sbt-test/pipelining/Yjava-tasty-paths/test new file mode 100644 index 000000000000..101a1b7cd2f1 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-paths/test @@ -0,0 +1,5 @@ +> a/compile +# test depending on variations of Java paths +> b/run +# double check against the real java classes +> bAlt/run diff --git a/tests/neg/i19619/InnerClass.java b/tests/neg/i19619/InnerClass.java new file mode 100644 index 000000000000..32bb1642a232 --- /dev/null +++ b/tests/neg/i19619/InnerClass.java @@ -0,0 +1,67 @@ +// InnerClass.java + +package lib; + +public class InnerClass { + + public class Inner { + public U innerField; + + public Inner(U innerField) { + this.innerField = innerField; + } + + public U getInnerField() { + return innerField; + } + } + + public class Outer { + + public class Nested { + + public U outerField; + public V innerField; + + public Nested(U outerField, V innerField) { + this.outerField = outerField; + this.innerField = innerField; + } + + public U getOuterField() { + return outerField; + } + + public V getInnerField() { + return innerField; + } + } + } + + public Inner createInner(U innerField) { + return new Inner<>(innerField); + } + + public Outer.Nested createNested(U outerField, V innerField) { + Outer outer = new Outer<>(); + return outer.new Nested<>(outerField, innerField); + } + + public static InnerClass.Inner createInnerStatic(U innerField) { + InnerClass innerClass = new InnerClass(); + return innerClass.new Inner<>(innerField); + } + + public static InnerClass.Outer.Nested createNestedStatic(U outerField, V innerField) { + InnerClass innerClass = new InnerClass(); + InnerClass.Outer outer = innerClass.new Outer<>(); + return outer.new Nested<>(outerField, innerField); + } + + public static void consumeNestedStatic(InnerClass.Outer.Nested nested) { + } + + public static void consumeNestedStatic2(Outer.Nested nested) { + } + +} diff --git a/tests/neg/i19619/InnerClassGen.java b/tests/neg/i19619/InnerClassGen.java new file mode 100644 index 000000000000..3a691aa0608f --- /dev/null +++ b/tests/neg/i19619/InnerClassGen.java @@ -0,0 +1,80 @@ +// InnerClassGen.java + +package lib; + +public class InnerClassGen { + + public class Inner { + public T rootField; + public U innerField; + + public Inner(T rootField, U innerField) { + this.rootField = rootField; + this.innerField = innerField; + } + + public T getRootField() { + return rootField; + } + + public U getInnerField() { + return innerField; + } + } + + public class Outer { + + public class Nested { + public T rootField; + public U outerField; + public V innerField; + + public Nested(T rootField, U outerField, V innerField) { + this.rootField = rootField; + this.outerField = outerField; + this.innerField = innerField; + } + + public T getRootField() { + return rootField; + } + + public U getOuterField() { + return outerField; + } + + public V getInnerField() { + return innerField; + } + } + } + + public static class OuterStatic { + public static class NestedStatic { + } + } + + public Inner createInner(T rootField, U innerField) { + return new Inner<>(rootField, innerField); + } + + public Outer.Nested createNested(T rootField, U outerField, V innerField) { + Outer outer = new Outer<>(); + return outer.new Nested<>(rootField, outerField, innerField); + } + + public static InnerClassGen.Inner createInnerStatic(T rootField, U innerField) { + InnerClassGen innerClassGen = new InnerClassGen<>(); + return innerClassGen.new Inner<>(rootField, innerField); + } + + public static InnerClassGen.Outer.Nested createNestedStatic(T rootField, U outerField, V innerField) { + InnerClassGen innerClassGen = new InnerClassGen<>(); + InnerClassGen.Outer outer = innerClassGen.new Outer<>(); + return outer.new Nested<>(rootField, outerField, innerField); + } + + public static void consumeNestedStatic(InnerClassGen.Outer.Nested nested) { + } + +} diff --git a/tests/neg/i19619/Test.scala b/tests/neg/i19619/Test.scala new file mode 100644 index 000000000000..85f9b3e529ac --- /dev/null +++ b/tests/neg/i19619/Test.scala @@ -0,0 +1,12 @@ +import lib.InnerClass +import lib.InnerClassGen + +@main def Test = + + locally: + val ici: InnerClass = new InnerClass() + val ici_inner1: ici.Inner[Long] = ici.createInner[Long](47L) // error + + locally: + val ici: InnerClassGen[String] = new InnerClassGen() + val ici_inner1: ici.Inner[Long] = ici.createInner[Long]("Hello", 47L) // error diff --git a/tests/run/i19619/Test.scala b/tests/run/i19619/Test.scala index 7e10950c6cfd..5d6f4d11c7a1 100644 --- a/tests/run/i19619/Test.scala +++ b/tests/run/i19619/Test.scala @@ -6,7 +6,7 @@ import lib.RawTypes locally: val ici: InnerClass = new InnerClass() - val ici_inner1: ici.Inner[Long] = ici.createInner[Long](47L) // ok, but should error + // val ici_inner1: ici.Inner[Long] = ici.createInner[Long](47L) // error val ici_inner2: InnerClass#Inner[Long] = ici.createInner[Long](47L) val ici_inner3: InnerClass#Inner[Long] = InnerClass.createInnerStatic[Long](47L) @@ -16,11 +16,11 @@ import lib.RawTypes val ici_nested3: InnerClass#Outer[Long]#Nested[Int] = InnerClass.createNestedStatic[Long, Int](47L, 23) InnerClass.consumeNestedStatic(ici_nested3) - InnerClass.consumeNestedStatic2(ici_nested3) // error: dotty is inferring static selection when there is no explicit prefix + InnerClass.consumeNestedStatic2(ici_nested3) locally: val ici: InnerClassGen[String] = new InnerClassGen() - val ici_inner1: ici.Inner[Long] = ici.createInner[Long]("Hello", 47L) // ok, but should error + // val ici_inner1: ici.Inner[Long] = ici.createInner[Long]("Hello", 47L) // error val ici_inner2: InnerClassGen[String]#Inner[Long] = ici.createInner[Long]("Hello", 47L) val ici_inner3: InnerClassGen[String]#Inner[Long] = InnerClassGen.createInnerStatic[String, Long]("Hello", 47L) @@ -39,4 +39,4 @@ import lib.RawTypes val cd_ii_Raw: RawTypes#C[?]#D[?] = cd_ii RawTypes.mii_Raw_Raw(cd_ii_Raw) - RawTypes.mii_Raw_Raw2(cd_ii_Raw) // error: dotty is inferring static selection when there is no explicit prefix + RawTypes.mii_Raw_Raw2(cd_ii_Raw) From 7d35282371f06421b8193d178284b5ed805b90b6 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Mon, 1 Jul 2024 23:48:18 +0200 Subject: [PATCH 23/40] also cook the tree of a java raw type [Cherry-picked e8e295afe3bf1d0452ecf0a515346c5986f33da7][modified] --- .../src/dotty/tools/dotc/typer/Typer.scala | 18 ++++++++++-------- .../pipelining/Yjava-tasty-paths/b/Test.scala | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index f0be4ac75b9f..43e5d52d397a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4214,14 +4214,16 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val tree1 = if ((pt eq AnyTypeConstructorProto) || tp.typeParamSymbols.isEmpty) tree else { - val tp1 = - if (ctx.isJava) - // Cook raw type - AppliedType(tree.tpe, tp.typeParams.map(Function.const(TypeBounds.empty))) - else - // Eta-expand higher-kinded type - tree.tpe.EtaExpand(tp.typeParamSymbols) - tree.withType(tp1) + if (ctx.isJava) + // Cook raw type + val typeArgs = tp.typeParams.map(Function.const(TypeBounds.empty)) + val tree1 = AppliedTypeTree(tree, typeArgs.map(TypeTree(_))) + val tp1 = AppliedType(tree.tpe, typeArgs) + tree1.withType(tp1) + else + // Eta-expand higher-kinded type + val tp1 = tree.tpe.EtaExpand(tp.typeParamSymbols) + tree.withType(tp1) } if (ctx.mode.is(Mode.Pattern) || ctx.mode.is(Mode.QuotedPattern) || tree1.tpe <:< pt) tree1 else err.typeMismatch(tree1, pt) diff --git a/sbt-test/pipelining/Yjava-tasty-paths/b/Test.scala b/sbt-test/pipelining/Yjava-tasty-paths/b/Test.scala index 4f555d3b8ac9..084469e76d8b 100644 --- a/sbt-test/pipelining/Yjava-tasty-paths/b/Test.scala +++ b/sbt-test/pipelining/Yjava-tasty-paths/b/Test.scala @@ -43,7 +43,7 @@ object B { val cd_ii_Raw: RawTypes#C[?]#D[?] = cd_ii RawTypes.mii_Raw_Raw(cd_ii_Raw) - // RawTypes.mii_Raw_Raw2(cd_ii_Raw) // error: dotty still doesnt rewrite the tree of a raw type to a type with wildcards + RawTypes.mii_Raw_Raw2(cd_ii_Raw) } } From f49f36a09cd47e11381919ead8adeab0ebd3a6f9 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 21 Feb 2024 18:02:42 +0100 Subject: [PATCH 24/40] In Java sources, allow a non-stable prefix for a class parent. (If we widen Foo.this.Inner in the parents of a class to AbstractFoo#Inner, i.e. an inherited class Inner prefixed by its owner abstract class AbstractFoo, this is not considered a stable prefix for a class parent type. But in Java this is the correct type so allow it.) [Cherry-picked d37857c1e1a921c865a285a377aa52d52d9152d5] --- .../src/dotty/tools/dotc/typer/Namer.scala | 12 +++-- .../Yjava-tasty-paths/a/InnerClassSub.java | 52 ++++++++++++++++++ .../pipelining/Yjava-tasty-paths/b/Test.scala | 18 +++++++ tests/run/i19619/InnerClassSub.java | 54 +++++++++++++++++++ tests/run/i19619/Test.scala | 17 ++++++ 5 files changed, 149 insertions(+), 4 deletions(-) create mode 100644 sbt-test/pipelining/Yjava-tasty-paths/a/InnerClassSub.java create mode 100644 tests/run/i19619/InnerClassSub.java diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 9e4768922ad3..e9b00cf95cac 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1539,17 +1539,19 @@ class Namer { typer: Typer => end parentType /** Check parent type tree `parent` for the following well-formedness conditions: - * (1) It must be a class type with a stable prefix (@see checkClassTypeWithStablePrefix) + * (1) It must be a class type with a stable prefix (unless `isJava`) (@see checkClassTypeWithStablePrefix) * (2) If may not derive from itself * (3) The class is not final * (4) If the class is sealed, it is defined in the same compilation unit as the current class + * + * @param isJava If true, the parent type is in Java mode, and we do not require a stable prefix */ - def checkedParentType(parent: untpd.Tree): Type = { + def checkedParentType(parent: untpd.Tree, isJava: Boolean): Type = { val ptype = parentType(parent)(using completerCtx.superCallContext).dealiasKeepAnnots if (cls.isRefinementClass) ptype else { val pt = checkClassType(ptype, parent.srcPos, - traitReq = parent ne parents.head, stablePrefixReq = true) + traitReq = parent ne parents.head, stablePrefixReq = !isJava) if (pt.derivesFrom(cls)) { val addendum = parent match { case Select(qual: Super, _) if Feature.migrateTo3 => @@ -1618,7 +1620,9 @@ class Namer { typer: Typer => val parentTypes = defn.adjustForTuple(cls, cls.typeParams, defn.adjustForBoxedUnit(cls, addUsingTraits( - ensureFirstIsClass(cls, parents.map(checkedParentType(_))) + locally: + val isJava = ctx.isJava + ensureFirstIsClass(cls, parents.map(checkedParentType(_, isJava))) ) ) ) diff --git a/sbt-test/pipelining/Yjava-tasty-paths/a/InnerClassSub.java b/sbt-test/pipelining/Yjava-tasty-paths/a/InnerClassSub.java new file mode 100644 index 000000000000..6441d71a4b20 --- /dev/null +++ b/sbt-test/pipelining/Yjava-tasty-paths/a/InnerClassSub.java @@ -0,0 +1,52 @@ +package a; + +public class InnerClassSub extends InnerClass { + + public class InnerSub extends Inner { + public InnerSub(U innerField) { + super(innerField); + } + } + + public class OuterSub extends Outer { + public OuterSub() { + super(); + } + } + + public Inner createInnerSub(U innerField) { + return new InnerSub<>(innerField); + } + + public Outer.Nested createNestedSub(U outerField, V innerField) { + OuterSub outer = new OuterSub<>(); + return outer.new Nested<>(outerField, innerField); + } + + public InnerClass.Inner createInnerSub2(U innerField) { + return new InnerSub<>(innerField); + } + + public InnerClass.Outer.Nested createNestedSub2(U outerField, V innerField) { + OuterSub outer = new OuterSub<>(); + return outer.new Nested<>(outerField, innerField); + } + + public static InnerClass.Inner createInnerStatic(U innerField) { + InnerClassSub innerClass = new InnerClassSub(); + return innerClass.new Inner<>(innerField); + } + + public static InnerClass.Outer.Nested createNestedStatic(U outerField, V innerField) { + InnerClassSub innerClass = new InnerClassSub(); + InnerClassSub.Outer outer = innerClass.new Outer<>(); + return outer.new Nested<>(outerField, innerField); + } + + public static void consumeNestedStatic(InnerClass.Outer.Nested nested) { + } + + public static void consumeNestedStatic2(Outer.Nested nested) { + } + +} diff --git a/sbt-test/pipelining/Yjava-tasty-paths/b/Test.scala b/sbt-test/pipelining/Yjava-tasty-paths/b/Test.scala index 084469e76d8b..bad55e361aa7 100644 --- a/sbt-test/pipelining/Yjava-tasty-paths/b/Test.scala +++ b/sbt-test/pipelining/Yjava-tasty-paths/b/Test.scala @@ -3,6 +3,7 @@ package b import a.InnerClass import a.InnerClassGen import a.RawTypes +import a.InnerClassSub object B { @main def test = { @@ -45,6 +46,23 @@ object B { RawTypes.mii_Raw_Raw(cd_ii_Raw) RawTypes.mii_Raw_Raw2(cd_ii_Raw) } + + locally { + val ici: InnerClassSub = new InnerClassSub() + // val ici_inner1: ici.Inner[Long] = ici.createInner[Long](47L) // error + val ici_inner2: InnerClass#Inner[Long] = ici.createInnerSub[Long](47L) + val ici_inner2_2: InnerClass#Inner[Long] = ici.createInnerSub2[Long](47L) + val ici_inner3: InnerClass#Inner[Long] = InnerClassSub.createInnerStatic[Long](47L) + + val ici_outer: InnerClassSub#Outer[Long] = new ici.Outer[Long]() + val ici_nested1: InnerClassSub#Outer[Long]#Nested[Int] = new ici_outer.Nested[Int](47L, 23) + val ici_nested2: InnerClass#Outer[Long]#Nested[Int] = ici.createNestedSub[Long, Int](47L, 23) + val ici_nested2_2: InnerClass#Outer[Long]#Nested[Int] = ici.createNestedSub2[Long, Int](47L, 23) + val ici_nested3: InnerClass#Outer[Long]#Nested[Int] = InnerClassSub.createNestedStatic[Long, Int](47L, 23) + + InnerClass.consumeNestedStatic(ici_nested3) + InnerClass.consumeNestedStatic2(ici_nested3) + } } } diff --git a/tests/run/i19619/InnerClassSub.java b/tests/run/i19619/InnerClassSub.java new file mode 100644 index 000000000000..5a1d1f4d3857 --- /dev/null +++ b/tests/run/i19619/InnerClassSub.java @@ -0,0 +1,54 @@ +// InnerClass.java + +package lib; + +public class InnerClassSub extends InnerClass { + + public class InnerSub extends Inner { + public InnerSub(U innerField) { + super(innerField); + } + } + + public class OuterSub extends Outer { + public OuterSub() { + super(); + } + } + + public Inner createInnerSub(U innerField) { + return new InnerSub<>(innerField); + } + + public Outer.Nested createNestedSub(U outerField, V innerField) { + OuterSub outer = new OuterSub<>(); + return outer.new Nested<>(outerField, innerField); + } + + public InnerClass.Inner createInnerSub2(U innerField) { + return new InnerSub<>(innerField); + } + + public InnerClass.Outer.Nested createNestedSub2(U outerField, V innerField) { + OuterSub outer = new OuterSub<>(); + return outer.new Nested<>(outerField, innerField); + } + + public static InnerClass.Inner createInnerStatic(U innerField) { + InnerClassSub innerClass = new InnerClassSub(); + return innerClass.new Inner<>(innerField); + } + + public static InnerClass.Outer.Nested createNestedStatic(U outerField, V innerField) { + InnerClassSub innerClass = new InnerClassSub(); + InnerClassSub.Outer outer = innerClass.new Outer<>(); + return outer.new Nested<>(outerField, innerField); + } + + public static void consumeNestedStatic(InnerClass.Outer.Nested nested) { + } + + public static void consumeNestedStatic2(Outer.Nested nested) { + } + +} diff --git a/tests/run/i19619/Test.scala b/tests/run/i19619/Test.scala index 5d6f4d11c7a1..8e8def4994db 100644 --- a/tests/run/i19619/Test.scala +++ b/tests/run/i19619/Test.scala @@ -1,6 +1,7 @@ import lib.InnerClass import lib.InnerClassGen import lib.RawTypes +import lib.InnerClassSub @main def Test = @@ -40,3 +41,19 @@ import lib.RawTypes RawTypes.mii_Raw_Raw(cd_ii_Raw) RawTypes.mii_Raw_Raw2(cd_ii_Raw) + + locally: + val ici: InnerClassSub = new InnerClassSub() + // val ici_inner1: ici.Inner[Long] = ici.createInner[Long](47L) // error + val ici_inner2: InnerClass#Inner[Long] = ici.createInnerSub[Long](47L) + val ici_inner2_2: InnerClass#Inner[Long] = ici.createInnerSub2[Long](47L) + val ici_inner3: InnerClass#Inner[Long] = InnerClassSub.createInnerStatic[Long](47L) + + val ici_outer: InnerClassSub#Outer[Long] = new ici.Outer[Long]() + val ici_nested1: InnerClassSub#Outer[Long]#Nested[Int] = new ici_outer.Nested[Int](47L, 23) + val ici_nested2: InnerClass#Outer[Long]#Nested[Int] = ici.createNestedSub[Long, Int](47L, 23) + val ici_nested2_2: InnerClass#Outer[Long]#Nested[Int] = ici.createNestedSub2[Long, Int](47L, 23) + val ici_nested3: InnerClass#Outer[Long]#Nested[Int] = InnerClassSub.createNestedStatic[Long, Int](47L, 23) + + InnerClass.consumeNestedStatic(ici_nested3) + InnerClass.consumeNestedStatic2(ici_nested3) From c190fc8120770c4b7eb94436ad17102d093871d1 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Thu, 22 Feb 2024 14:30:58 +0100 Subject: [PATCH 25/40] skip test on scala-js [Cherry-picked faf8547ecd0cdf4c50d7b789e8fba22c6b6efde3] --- tests/run/i19619/Test.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/run/i19619/Test.scala b/tests/run/i19619/Test.scala index 8e8def4994db..871e7a490ea0 100644 --- a/tests/run/i19619/Test.scala +++ b/tests/run/i19619/Test.scala @@ -1,3 +1,5 @@ +// scalajs: --skip + import lib.InnerClass import lib.InnerClassGen import lib.RawTypes From 90835e6e34c4b83cdb3746348517e3178dd92dd1 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Mon, 1 Jul 2024 23:58:57 +0200 Subject: [PATCH 26/40] Remove not-applicable sbt-test/pipelining from LTS --- .../Yjava-tasty-paths/a/InnerClass.java | 65 ---------------- .../Yjava-tasty-paths/a/InnerClassGen.java | 78 ------------------- .../Yjava-tasty-paths/a/InnerClassSub.java | 52 ------------- .../Yjava-tasty-paths/a/RawTypes.java | 16 ---- .../Yjava-tasty-paths/a/package.scala | 2 - .../pipelining/Yjava-tasty-paths/b/Test.scala | 68 ---------------- .../pipelining/Yjava-tasty-paths/build.sbt | 31 -------- .../project/DottyInjectedPlugin.scala | 12 --- sbt-test/pipelining/Yjava-tasty-paths/test | 5 -- 9 files changed, 329 deletions(-) delete mode 100644 sbt-test/pipelining/Yjava-tasty-paths/a/InnerClass.java delete mode 100644 sbt-test/pipelining/Yjava-tasty-paths/a/InnerClassGen.java delete mode 100644 sbt-test/pipelining/Yjava-tasty-paths/a/InnerClassSub.java delete mode 100644 sbt-test/pipelining/Yjava-tasty-paths/a/RawTypes.java delete mode 100644 sbt-test/pipelining/Yjava-tasty-paths/a/package.scala delete mode 100644 sbt-test/pipelining/Yjava-tasty-paths/b/Test.scala delete mode 100644 sbt-test/pipelining/Yjava-tasty-paths/build.sbt delete mode 100644 sbt-test/pipelining/Yjava-tasty-paths/project/DottyInjectedPlugin.scala delete mode 100644 sbt-test/pipelining/Yjava-tasty-paths/test diff --git a/sbt-test/pipelining/Yjava-tasty-paths/a/InnerClass.java b/sbt-test/pipelining/Yjava-tasty-paths/a/InnerClass.java deleted file mode 100644 index c23bc54397dd..000000000000 --- a/sbt-test/pipelining/Yjava-tasty-paths/a/InnerClass.java +++ /dev/null @@ -1,65 +0,0 @@ -package a; - -public class InnerClass { - - public class Inner { - public U innerField; - - public Inner(U innerField) { - this.innerField = innerField; - } - - public U getInnerField() { - return innerField; - } - } - - public class Outer { - - public class Nested { - - public U outerField; - public V innerField; - - public Nested(U outerField, V innerField) { - this.outerField = outerField; - this.innerField = innerField; - } - - public U getOuterField() { - return outerField; - } - - public V getInnerField() { - return innerField; - } - } - } - - public Inner createInner(U innerField) { - return new Inner<>(innerField); - } - - public Outer.Nested createNested(U outerField, V innerField) { - Outer outer = new Outer<>(); - return outer.new Nested<>(outerField, innerField); - } - - public static InnerClass.Inner createInnerStatic(U innerField) { - InnerClass innerClass = new InnerClass(); - return innerClass.new Inner<>(innerField); - } - - public static InnerClass.Outer.Nested createNestedStatic(U outerField, V innerField) { - InnerClass innerClass = new InnerClass(); - InnerClass.Outer outer = innerClass.new Outer<>(); - return outer.new Nested<>(outerField, innerField); - } - - public static void consumeNestedStatic(InnerClass.Outer.Nested nested) { - } - - public static void consumeNestedStatic2(Outer.Nested nested) { - } - -} diff --git a/sbt-test/pipelining/Yjava-tasty-paths/a/InnerClassGen.java b/sbt-test/pipelining/Yjava-tasty-paths/a/InnerClassGen.java deleted file mode 100644 index 4011cf2fc244..000000000000 --- a/sbt-test/pipelining/Yjava-tasty-paths/a/InnerClassGen.java +++ /dev/null @@ -1,78 +0,0 @@ -package a; - -public class InnerClassGen { - - public class Inner { - public T rootField; - public U innerField; - - public Inner(T rootField, U innerField) { - this.rootField = rootField; - this.innerField = innerField; - } - - public T getRootField() { - return rootField; - } - - public U getInnerField() { - return innerField; - } - } - - public class Outer { - - public class Nested { - public T rootField; - public U outerField; - public V innerField; - - public Nested(T rootField, U outerField, V innerField) { - this.rootField = rootField; - this.outerField = outerField; - this.innerField = innerField; - } - - public T getRootField() { - return rootField; - } - - public U getOuterField() { - return outerField; - } - - public V getInnerField() { - return innerField; - } - } - } - - public static class OuterStatic { - public static class NestedStatic { - } - } - - public Inner createInner(T rootField, U innerField) { - return new Inner<>(rootField, innerField); - } - - public Outer.Nested createNested(T rootField, U outerField, V innerField) { - Outer outer = new Outer<>(); - return outer.new Nested<>(rootField, outerField, innerField); - } - - public static InnerClassGen.Inner createInnerStatic(T rootField, U innerField) { - InnerClassGen innerClassGen = new InnerClassGen<>(); - return innerClassGen.new Inner<>(rootField, innerField); - } - - public static InnerClassGen.Outer.Nested createNestedStatic(T rootField, U outerField, V innerField) { - InnerClassGen innerClassGen = new InnerClassGen<>(); - InnerClassGen.Outer outer = innerClassGen.new Outer<>(); - return outer.new Nested<>(rootField, outerField, innerField); - } - - public static void consumeNestedStatic(InnerClassGen.Outer.Nested nested) { - } - -} diff --git a/sbt-test/pipelining/Yjava-tasty-paths/a/InnerClassSub.java b/sbt-test/pipelining/Yjava-tasty-paths/a/InnerClassSub.java deleted file mode 100644 index 6441d71a4b20..000000000000 --- a/sbt-test/pipelining/Yjava-tasty-paths/a/InnerClassSub.java +++ /dev/null @@ -1,52 +0,0 @@ -package a; - -public class InnerClassSub extends InnerClass { - - public class InnerSub extends Inner { - public InnerSub(U innerField) { - super(innerField); - } - } - - public class OuterSub extends Outer { - public OuterSub() { - super(); - } - } - - public Inner createInnerSub(U innerField) { - return new InnerSub<>(innerField); - } - - public Outer.Nested createNestedSub(U outerField, V innerField) { - OuterSub outer = new OuterSub<>(); - return outer.new Nested<>(outerField, innerField); - } - - public InnerClass.Inner createInnerSub2(U innerField) { - return new InnerSub<>(innerField); - } - - public InnerClass.Outer.Nested createNestedSub2(U outerField, V innerField) { - OuterSub outer = new OuterSub<>(); - return outer.new Nested<>(outerField, innerField); - } - - public static InnerClass.Inner createInnerStatic(U innerField) { - InnerClassSub innerClass = new InnerClassSub(); - return innerClass.new Inner<>(innerField); - } - - public static InnerClass.Outer.Nested createNestedStatic(U outerField, V innerField) { - InnerClassSub innerClass = new InnerClassSub(); - InnerClassSub.Outer outer = innerClass.new Outer<>(); - return outer.new Nested<>(outerField, innerField); - } - - public static void consumeNestedStatic(InnerClass.Outer.Nested nested) { - } - - public static void consumeNestedStatic2(Outer.Nested nested) { - } - -} diff --git a/sbt-test/pipelining/Yjava-tasty-paths/a/RawTypes.java b/sbt-test/pipelining/Yjava-tasty-paths/a/RawTypes.java deleted file mode 100644 index 4f97c45e7f90..000000000000 --- a/sbt-test/pipelining/Yjava-tasty-paths/a/RawTypes.java +++ /dev/null @@ -1,16 +0,0 @@ -package a; - -public class RawTypes { - - public class C { - public class D { - } - } - - public static void mii_Raw_Raw(RawTypes.C.D d) { - } - - public static void mii_Raw_Raw2(C.D d) { - } - -} diff --git a/sbt-test/pipelining/Yjava-tasty-paths/a/package.scala b/sbt-test/pipelining/Yjava-tasty-paths/a/package.scala deleted file mode 100644 index 93f99e9892fe..000000000000 --- a/sbt-test/pipelining/Yjava-tasty-paths/a/package.scala +++ /dev/null @@ -1,2 +0,0 @@ -// THIS FILE EXISTS SO THAT `A.java` WILL BE COMPILED BY SCALAC -package a diff --git a/sbt-test/pipelining/Yjava-tasty-paths/b/Test.scala b/sbt-test/pipelining/Yjava-tasty-paths/b/Test.scala deleted file mode 100644 index bad55e361aa7..000000000000 --- a/sbt-test/pipelining/Yjava-tasty-paths/b/Test.scala +++ /dev/null @@ -1,68 +0,0 @@ -package b - -import a.InnerClass -import a.InnerClassGen -import a.RawTypes -import a.InnerClassSub - -object B { - @main def test = { - locally { - val ici: InnerClass = new InnerClass() - // val ici_inner1: ici.Inner[Long] = ici.createInner[Long](47L) // error - val ici_inner2: InnerClass#Inner[Long] = ici.createInner[Long](47L) - val ici_inner3: InnerClass#Inner[Long] = InnerClass.createInnerStatic[Long](47L) - - val ici_outer: InnerClass#Outer[Long] = new ici.Outer[Long]() - val ici_nested1: InnerClass#Outer[Long]#Nested[Int] = new ici_outer.Nested[Int](47L, 23) - val ici_nested2: InnerClass#Outer[Long]#Nested[Int] = ici.createNested[Long, Int](47L, 23) - val ici_nested3: InnerClass#Outer[Long]#Nested[Int] = InnerClass.createNestedStatic[Long, Int](47L, 23) - - InnerClass.consumeNestedStatic(ici_nested3) - InnerClass.consumeNestedStatic2(ici_nested3) - } - - locally { - val ici: InnerClassGen[String] = new InnerClassGen() - // val ici_inner1: ici.Inner[Long] = ici.createInner[Long]("Hello", 47L) // error - val ici_inner2: InnerClassGen[String]#Inner[Long] = ici.createInner[Long]("Hello", 47L) - val ici_inner3: InnerClassGen[String]#Inner[Long] = InnerClassGen.createInnerStatic[String, Long]("Hello", 47L) - - val ici_outer: InnerClassGen[String]#Outer[Long] = new ici.Outer[Long]() - val ici_nested1: InnerClassGen[String]#Outer[Long]#Nested[Int] = new ici_outer.Nested[Int]("Hello", 47L, 23) - val ici_nested2: InnerClassGen[String]#Outer[Long]#Nested[Int] = ici.createNested[Long, Int]("Hello", 47L, 23) - val ici_nested3: InnerClassGen[String]#Outer[Long]#Nested[Int] = InnerClassGen.createNestedStatic[String, Long, Int]("Hello", 47L, 23) - - InnerClassGen.consumeNestedStatic(ici_nested3) - } - - locally { - val rt: RawTypes = new RawTypes() - val c: RawTypes#C[String] = new rt.C[String]() - - val cd_ii: RawTypes#C[String]#D[String] = new c.D[String]() - val cd_ii_Raw: RawTypes#C[?]#D[?] = cd_ii - - RawTypes.mii_Raw_Raw(cd_ii_Raw) - RawTypes.mii_Raw_Raw2(cd_ii_Raw) - } - - locally { - val ici: InnerClassSub = new InnerClassSub() - // val ici_inner1: ici.Inner[Long] = ici.createInner[Long](47L) // error - val ici_inner2: InnerClass#Inner[Long] = ici.createInnerSub[Long](47L) - val ici_inner2_2: InnerClass#Inner[Long] = ici.createInnerSub2[Long](47L) - val ici_inner3: InnerClass#Inner[Long] = InnerClassSub.createInnerStatic[Long](47L) - - val ici_outer: InnerClassSub#Outer[Long] = new ici.Outer[Long]() - val ici_nested1: InnerClassSub#Outer[Long]#Nested[Int] = new ici_outer.Nested[Int](47L, 23) - val ici_nested2: InnerClass#Outer[Long]#Nested[Int] = ici.createNestedSub[Long, Int](47L, 23) - val ici_nested2_2: InnerClass#Outer[Long]#Nested[Int] = ici.createNestedSub2[Long, Int](47L, 23) - val ici_nested3: InnerClass#Outer[Long]#Nested[Int] = InnerClassSub.createNestedStatic[Long, Int](47L, 23) - - InnerClass.consumeNestedStatic(ici_nested3) - InnerClass.consumeNestedStatic2(ici_nested3) - } - } - -} diff --git a/sbt-test/pipelining/Yjava-tasty-paths/build.sbt b/sbt-test/pipelining/Yjava-tasty-paths/build.sbt deleted file mode 100644 index d63d1f9a3f7e..000000000000 --- a/sbt-test/pipelining/Yjava-tasty-paths/build.sbt +++ /dev/null @@ -1,31 +0,0 @@ -lazy val a = project.in(file("a")) - .settings( - scalacOptions += "-Yjava-tasty", // enable pickling of java signatures - scalacOptions ++= Seq("-Yjava-tasty-output", ((ThisBuild / baseDirectory).value / "a-paths-java-tasty.jar").toString), - scalacOptions += "-Ycheck:all", - Compile / classDirectory := ((ThisBuild / baseDirectory).value / "a-paths-classes"), // send classfiles to a different directory - ) - -lazy val b = project.in(file("b")) - .settings( - Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-paths-java-tasty.jar")), - scalacOptions += "-Ycheck:all", - scalacOptions += "-explain-cyclic", - scalacOptions += "-Ydebug-cyclic", - ) - .settings( - fork := true, // we have to fork the JVM if we actually want to run the code with correct failure semantics - Runtime / unmanagedClasspath += Attributed.blank((ThisBuild / baseDirectory).value / "a-paths-classes"), // make sure the java classes are visible at runtime - ) - -// same as b, but adds the real classes to the classpath instead of the tasty jar -lazy val bAlt = project.in(file("b-alt")) - .settings( - Compile / sources := (b / Compile / sources).value, - Compile / unmanagedClasspath := Seq(Attributed.blank((ThisBuild / baseDirectory).value / "a-paths-classes")), - scalacOptions += "-Ycheck:all", - ) - .settings( - fork := true, // we have to fork the JVM if we actually want to run the code with correct failure semantics - Runtime / unmanagedClasspath += Attributed.blank((ThisBuild / baseDirectory).value / "a-paths-classes"), // make sure the java classes are visible at runtime - ) diff --git a/sbt-test/pipelining/Yjava-tasty-paths/project/DottyInjectedPlugin.scala b/sbt-test/pipelining/Yjava-tasty-paths/project/DottyInjectedPlugin.scala deleted file mode 100644 index 69f15d168bfc..000000000000 --- a/sbt-test/pipelining/Yjava-tasty-paths/project/DottyInjectedPlugin.scala +++ /dev/null @@ -1,12 +0,0 @@ -import sbt._ -import Keys._ - -object DottyInjectedPlugin extends AutoPlugin { - override def requires = plugins.JvmPlugin - override def trigger = allRequirements - - override val projectSettings = Seq( - scalaVersion := sys.props("plugin.scalaVersion"), - scalacOptions += "-source:3.0-migration" - ) -} diff --git a/sbt-test/pipelining/Yjava-tasty-paths/test b/sbt-test/pipelining/Yjava-tasty-paths/test deleted file mode 100644 index 101a1b7cd2f1..000000000000 --- a/sbt-test/pipelining/Yjava-tasty-paths/test +++ /dev/null @@ -1,5 +0,0 @@ -> a/compile -# test depending on variations of Java paths -> b/run -# double check against the real java classes -> bAlt/run From 0c60d27879c1afd834d4903cfbfda5205d54a090 Mon Sep 17 00:00:00 2001 From: i10416 Date: Sat, 24 Feb 2024 16:57:21 +0900 Subject: [PATCH 27/40] test: add test for #19762 Add a test to confirm issue#19762 is solved. [Cherry-picked 2a1d1b363afa61ea41b5149dc459908479088257] --- tests/neg/i19762.check | 6 ++++++ tests/neg/i19762.scala | 8 ++++++++ 2 files changed, 14 insertions(+) create mode 100644 tests/neg/i19762.check create mode 100644 tests/neg/i19762.scala diff --git a/tests/neg/i19762.check b/tests/neg/i19762.check new file mode 100644 index 000000000000..93def936e2a0 --- /dev/null +++ b/tests/neg/i19762.check @@ -0,0 +1,6 @@ +-- [E019] Syntax Error: tests/neg/i19762.scala:8:31 -------------------------------------------------------------------- +8 | def combine(x: Int, y: Int)) = x + y // error + | ^ + | Missing return type + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i19762.scala b/tests/neg/i19762.scala new file mode 100644 index 000000000000..4966f0d8cb53 --- /dev/null +++ b/tests/neg/i19762.scala @@ -0,0 +1,8 @@ +trait Monoid[A]: + def combine(x: A, y: A): A + def empty: A + +object Monoid: + lazy val addInt: Monoid[Int] = new: + val empty = 0 + def combine(x: Int, y: Int)) = x + y // error \ No newline at end of file From e8f46bad289aae2d8c2fb06080cb019dbc82fa43 Mon Sep 17 00:00:00 2001 From: Eugene Flesselle Date: Mon, 19 Feb 2024 17:36:34 +0100 Subject: [PATCH 28/40] Attempt implicit search for old style `implicit` parameters (not only `using` params) [Cherry-picked 1e890164b6dcb7d4f84a5df5135f56111ae74925] --- .../src/dotty/tools/dotc/typer/Applications.scala | 2 +- tests/neg/i19594.check | 8 ++++++++ tests/neg/i19594.scala | 13 +++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 tests/neg/i19594.check create mode 100644 tests/neg/i19594.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 99074313e8bb..e16645cb4190 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -620,7 +620,7 @@ trait Applications extends Compatibility { defaultArg.tpe.widen match case _: MethodOrPoly if testOnly => matchArgs(args1, formals1, n + 1) case _ => matchArgs(args1, addTyped(treeToArg(defaultArg)), n + 1) - else if methodType.isContextualMethod && ctx.mode.is(Mode.ImplicitsEnabled) then + else if methodType.isImplicitMethod && ctx.mode.is(Mode.ImplicitsEnabled) then matchArgs(args1, addTyped(treeToArg(implicitArg)), n + 1) else missingArg(n) diff --git a/tests/neg/i19594.check b/tests/neg/i19594.check new file mode 100644 index 000000000000..bb9ff3fc68af --- /dev/null +++ b/tests/neg/i19594.check @@ -0,0 +1,8 @@ +-- [E172] Type Error: tests/neg/i19594.scala:12:14 --------------------------------------------------------------------- +12 | assertEquals(true, 1, "values are not the same") // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Can you see me?! +-- [E172] Type Error: tests/neg/i19594.scala:13:14 --------------------------------------------------------------------- +13 | assertEquals(true, 1) // error + | ^^^^^^^^^^^^^^^^^^^^^ + | Can you see me?! diff --git a/tests/neg/i19594.scala b/tests/neg/i19594.scala new file mode 100644 index 000000000000..a559da8d9250 --- /dev/null +++ b/tests/neg/i19594.scala @@ -0,0 +1,13 @@ +import scala.annotation.implicitNotFound + +@implicitNotFound("Can you see me?!") +trait Compare[A, B] + +object example extends App: + + // The presence of the below default argument prevents the `implicitNotFound` message from appearing + def assertEquals[A, B](a: A, b: B, clue: => Any = "values are not the same") + (implicit comp: Compare[A, B]): Unit = () + + assertEquals(true, 1, "values are not the same") // error + assertEquals(true, 1) // error From 7d59a353808567c22341a0ea5b45b47b88511e6e Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Sat, 24 Feb 2024 23:33:42 +0100 Subject: [PATCH 29/40] Add a new test for selection range on function parameters [Cherry-picked 2e684d8fdf489cadce57a51759cc41725038b6f6] --- .../tools/pc/tests/SelectionRangeSuite.scala | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/SelectionRangeSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/SelectionRangeSuite.scala index 84417bb5e414..e277a67c466b 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/SelectionRangeSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/SelectionRangeSuite.scala @@ -101,3 +101,49 @@ class SelectionRangeSuite extends BaseSelectionRangeSuite: |}<>region>>a: Int<>region>>a: Int, b: Int<>region>>def func(a: Int, b: Int) = + | a + b< + | a + b + |}""".stripMargin, + List[String]( + """|object Main extends App { + | val func = (>>region>>a: Int< + | a + b + |}""".stripMargin, + """|object Main extends App { + | val func = (>>region>>a: Int, b: Int< + | a + b + |}""".stripMargin, + """|object Main extends App { + | val func = >>region>>(a: Int, b: Int) => + | a + b<>region>>val func = (a: Int, b: Int) => + | a + b< Date: Sun, 25 Feb 2024 00:25:26 +0100 Subject: [PATCH 30/40] Add selection range for a parameter list in a function/method Given a query as ```scala def func(a@@: Int, b: Int)(c: Int) = a + b + c ``` range selection will now let you choose `a: Int, b: Int`. The previous options are `a: Int` and the whole definition of `func`. [Cherry-picked 9f61ed5bcd38b7705b1479961e240243c9378195] --- .../tools/pc/SelectionRangeProvider.scala | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/presentation-compiler/src/main/dotty/tools/pc/SelectionRangeProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/SelectionRangeProvider.scala index a7d07b12f40c..9dce37028bea 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/SelectionRangeProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/SelectionRangeProvider.scala @@ -6,10 +6,12 @@ import java.util as ju import scala.jdk.CollectionConverters._ import scala.meta.pc.OffsetParams +import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.interactive.Interactive import dotty.tools.dotc.interactive.InteractiveDriver import dotty.tools.dotc.util.SourceFile +import dotty.tools.dotc.util.SourcePosition import dotty.tools.pc.utils.MtagsEnrichments.* import org.eclipse.lsp4j @@ -46,11 +48,7 @@ class SelectionRangeProvider( Interactive.pathTo(driver.openedTrees(uri), pos)(using ctx) val bareRanges = path - .map { tree => - val selectionRange = new SelectionRange() - selectionRange.setRange(tree.sourcePos.toLsp) - selectionRange - } + .flatMap(selectionRangesFromTree(pos)) val comments = driver.compilationUnits.get(uri).map(_.comments).toList.flatten @@ -79,6 +77,33 @@ class SelectionRangeProvider( } end selectionRange + /** Given a tree, create a seq of [[SelectionRange]]s corresponding to that tree. */ + private def selectionRangesFromTree(pos: SourcePosition)(tree: tpd.Tree)(using Context) = + def toSelectionRange(srcPos: SourcePosition) = + val selectionRange = new SelectionRange() + selectionRange.setRange(srcPos.toLsp) + selectionRange + + val treeSelectionRange = toSelectionRange(tree.sourcePos) + + tree match + case tpd.DefDef(name, paramss, tpt, rhs) => + // If source position is within a parameter list, add a selection range covering that whole list. + val selectedParams = + paramss + .iterator + .flatMap: // parameter list to a sourcePosition covering the whole list + case Seq(param) => Some(param.sourcePos) + case params @ Seq(head, tail*) => + val srcPos = head.sourcePos + val lastSpan = tail.last.span + Some(SourcePosition(srcPos.source, srcPos.span union lastSpan, srcPos.outer)) + case Seq() => None + .find(_.contains(pos)) + .map(toSelectionRange) + selectedParams ++ Seq(treeSelectionRange) + case _ => Seq(treeSelectionRange) + private def setParent( child: SelectionRange, parent: SelectionRange From 421a3d186e97d7539d8b5016568b4fc63e8a7b6e Mon Sep 17 00:00:00 2001 From: Hamza REMMAL Date: Tue, 20 Feb 2024 15:42:37 +0100 Subject: [PATCH 31/40] Bump JLine to 3.19.0 -> 3.24.1 [Cherry-picked a2212fa8c7a2f702e706dec1e04f67291824339e] --- project/Build.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 59321b5f677d..ed4be65dc095 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -579,9 +579,9 @@ object Build { libraryDependencies ++= Seq( "org.scala-lang.modules" % "scala-asm" % "9.6.0-scala-1", // used by the backend Dependencies.compilerInterface, - "org.jline" % "jline-reader" % "3.19.0", // used by the REPL - "org.jline" % "jline-terminal" % "3.19.0", - "org.jline" % "jline-terminal-jna" % "3.19.0", // needed for Windows + "org.jline" % "jline-reader" % "3.24.1", // used by the REPL + "org.jline" % "jline-terminal" % "3.24.1", + "org.jline" % "jline-terminal-jna" % "3.24.1", // needed for Windows ("io.get-coursier" %% "coursier" % "2.0.16" % Test).cross(CrossVersion.for3Use2_13), ), From 6ec5acdada8d9b356ba2338f9f056b339a226dd0 Mon Sep 17 00:00:00 2001 From: Hamza REMMAL Date: Sat, 24 Feb 2024 12:16:12 +0100 Subject: [PATCH 32/40] Bump sbt from 1.9.7 to 1.9.9 [Cherry-picked 29461f46ff5fb9acc4ea57de0ac114f1b254ea5d] --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index e8a1e246e8ad..04267b14af69 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.7 +sbt.version=1.9.9 From de16497ef8fa8ceccff03872304d44382a81507f Mon Sep 17 00:00:00 2001 From: Hamza REMMAL Date: Sun, 25 Feb 2024 01:09:14 +0100 Subject: [PATCH 33/40] Update Coursier test [Cherry-picked b7696fa711e113e2a6a957edeaac644557956cd6] --- .../dotty/tools/coursier/CoursierScalaTests.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/test-coursier/dotty/tools/coursier/CoursierScalaTests.scala b/compiler/test-coursier/dotty/tools/coursier/CoursierScalaTests.scala index 8f9a9bd69a50..b8dfa833c437 100644 --- a/compiler/test-coursier/dotty/tools/coursier/CoursierScalaTests.scala +++ b/compiler/test-coursier/dotty/tools/coursier/CoursierScalaTests.scala @@ -76,7 +76,7 @@ class CoursierScalaTests: def emptyArgsEqualsRepl() = val output = CoursierScalaTests.csScalaCmd() - assertTrue(output.mkString("\n").contains("Unable to create a system terminal")) // Scala attempted to create REPL so we can assume it is working + assertTrue(output.mkString("\n").contains("Unable to create a terminal")) // Scala attempted to create REPL so we can assume it is working emptyArgsEqualsRepl() def run() = @@ -133,7 +133,7 @@ class CoursierScalaTests: def replWithArgs() = val output = CoursierScalaTests.csScalaCmd("-source", "3.0-migration") - assertTrue(output.mkString("\n").contains("Unable to create a system terminal")) // Scala attempted to create REPL so we can assume it is working + assertTrue(output.mkString("\n").contains("Unable to create a terminal")) // Scala attempted to create REPL so we can assume it is working replWithArgs() def argumentFile() = From 15c3f9cfdd98f6351797e8c2ff8b092279e7ebab Mon Sep 17 00:00:00 2001 From: Hamza REMMAL Date: Mon, 26 Feb 2024 14:21:37 +0100 Subject: [PATCH 34/40] Bump JLine to 3.24.1-> 3.25.1 [Cherry-picked adc2205606bdcff6800a118d8edfe3700b8be439] --- project/Build.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index ed4be65dc095..8d045e50bba0 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -579,9 +579,9 @@ object Build { libraryDependencies ++= Seq( "org.scala-lang.modules" % "scala-asm" % "9.6.0-scala-1", // used by the backend Dependencies.compilerInterface, - "org.jline" % "jline-reader" % "3.24.1", // used by the REPL - "org.jline" % "jline-terminal" % "3.24.1", - "org.jline" % "jline-terminal-jna" % "3.24.1", // needed for Windows + "org.jline" % "jline-reader" % "3.25.1", // used by the REPL + "org.jline" % "jline-terminal" % "3.25.1", + "org.jline" % "jline-terminal-jna" % "3.25.1", // needed for Windows ("io.get-coursier" %% "coursier" % "2.0.16" % Test).cross(CrossVersion.for3Use2_13), ), From b70354e74a11cbc761d7838388948cb3d06e734e Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 21 Feb 2024 17:51:37 +0000 Subject: [PATCH 35/40] Test case for REPL bad symbolic reference [Cherry-picked 6db1423d8783e745b60b80b3b4ef54668b9d6318] --- .../test/dotty/tools/repl/ReplCompilerTests.scala | 14 ++++++++++++++ compiler/test/dotty/tools/repl/ReplTest.scala | 4 ++++ .../test/dotty/tools/repl/TabcompleteTests.scala | 4 ---- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/compiler/test/dotty/tools/repl/ReplCompilerTests.scala b/compiler/test/dotty/tools/repl/ReplCompilerTests.scala index ecdfeb512e1b..41c0b4c44568 100644 --- a/compiler/test/dotty/tools/repl/ReplCompilerTests.scala +++ b/compiler/test/dotty/tools/repl/ReplCompilerTests.scala @@ -353,6 +353,20 @@ class ReplCompilerTests extends ReplTest: @Test def `i13097 expect template after colon` = contextually: assert(ParseResult.isIncomplete("class C:")) + @Test def i15562: Unit = initially { + val s1 = run("List(1, 2).filter(_ % 2 == 0).foreach(println)") + assertEquals("2", storedOutput().trim) + s1 + } andThen { s1 ?=> + val comp = tabComplete("List(1, 2).filter(_ % 2 == 0).fore") + assertEquals(List("foreach"), comp.distinct) + s1 + } andThen { + val s2 = run("List(1, 2).filter(_ % 2 == 0).foreach(println)") + assertEquals("2", storedOutput().trim) + s2 + } + object ReplCompilerTests: private val pattern = Pattern.compile("\\r[\\n]?|\\n"); diff --git a/compiler/test/dotty/tools/repl/ReplTest.scala b/compiler/test/dotty/tools/repl/ReplTest.scala index 8fbf635c9a17..5f185e9c9e29 100644 --- a/compiler/test/dotty/tools/repl/ReplTest.scala +++ b/compiler/test/dotty/tools/repl/ReplTest.scala @@ -40,6 +40,10 @@ extends ReplDriver(options, new PrintStream(out, true, StandardCharsets.UTF_8.na def contextually[A](op: Context ?=> A): A = op(using initialState.context) + /** Returns the `(, )`*/ + def tabComplete(src: String)(implicit state: State): List[String] = + completions(src.length, src, state).map(_.value).sorted + extension [A](state: State) infix def andThen(op: State ?=> A): A = op(using state) diff --git a/compiler/test/dotty/tools/repl/TabcompleteTests.scala b/compiler/test/dotty/tools/repl/TabcompleteTests.scala index 0bce525e1469..e4c3a2557e7d 100644 --- a/compiler/test/dotty/tools/repl/TabcompleteTests.scala +++ b/compiler/test/dotty/tools/repl/TabcompleteTests.scala @@ -8,10 +8,6 @@ import org.junit.Test /** These tests test input that has proved problematic */ class TabcompleteTests extends ReplTest { - /** Returns the `(, )`*/ - private def tabComplete(src: String)(implicit state: State): List[String] = - completions(src.length, src, state).map(_.value).sorted - @Test def tabCompleteList = initially { val comp = tabComplete("List.r") assertEquals(List("range"), comp.distinct) From 858f974b77f126e2e2831874a3529c0505da2b35 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 21 Feb 2024 22:08:06 +0000 Subject: [PATCH 36/40] Add a test case which still fails [Cherry-picked c2c78d7312ba882a1c7be3c1369e753282ed7bd9] --- .../test/dotty/tools/repl/ReplCompilerTests.scala | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/compiler/test/dotty/tools/repl/ReplCompilerTests.scala b/compiler/test/dotty/tools/repl/ReplCompilerTests.scala index 41c0b4c44568..63da0e277bb4 100644 --- a/compiler/test/dotty/tools/repl/ReplCompilerTests.scala +++ b/compiler/test/dotty/tools/repl/ReplCompilerTests.scala @@ -367,6 +367,20 @@ class ReplCompilerTests extends ReplTest: s2 } + @Test def i15562b: Unit = initially { + val s1 = run("List(1, 2).filter(_ % 2 == 0).foreach(println)") + assertEquals("2", storedOutput().trim) + s1 + } andThen { s1 ?=> + val comp = tabComplete("val x = false + true; List(1, 2).filter(_ % 2 == 0).fore") + assertEquals(List("foreach"), comp.distinct) + s1 + } andThen { + val s2 = run("List(1, 2).filter(_ % 2 == 0).foreach(println)") + assertEquals("2", storedOutput().trim) + s2 + } + object ReplCompilerTests: private val pattern = Pattern.compile("\\r[\\n]?|\\n"); From 3d3d8aaf58c20252c2b00ae381dd649f572baccf Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 23 Feb 2024 17:22:37 +0000 Subject: [PATCH 37/40] Avoid losing the symbols denotation on update [Cherry-picked 4615a9c4ef576405ab61680a3f2ba9a11c0b33fc] --- compiler/src/dotty/tools/dotc/core/Symbols.scala | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index eb60d76c77c4..2786fcf3610c 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -107,16 +107,23 @@ object Symbols extends SymUtils { private def computeDenot(lastd: SymDenotation)(using Context): SymDenotation = { util.Stats.record("Symbol.computeDenot") val now = ctx.period + val prev = checkedPeriod checkedPeriod = now - if (lastd.validFor contains now) lastd else recomputeDenot(lastd) + if lastd.validFor.contains(now) then + lastd + else + val newd = recomputeDenot(lastd) + if newd.exists then + lastDenot = newd + else + checkedPeriod = prev + newd } /** Overridden in NoSymbol */ protected def recomputeDenot(lastd: SymDenotation)(using Context): SymDenotation = { util.Stats.record("Symbol.recomputeDenot") - val newd = lastd.current.asInstanceOf[SymDenotation] - lastDenot = newd - newd + lastd.current.asSymDenotation } /** The original denotation of this symbol, without forcing anything */ From e916b4a3940ba5b23317b3d32394e28486132c20 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 26 Feb 2024 13:59:52 +0100 Subject: [PATCH 38/40] Some tweaks to denotation updates - Be more specific when we go into the special case of not updating checkedPeriod - Always update lastDenotation. - Keep computeDenot small, move work to recomputeDenot [Cherry-picked e79b2e99800d2d06696e04192a63ce0e6f22bff8] --- .../src/dotty/tools/dotc/core/Symbols.scala | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 2786fcf3610c..40528f2f0e5a 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -105,25 +105,28 @@ object Symbols extends SymUtils { } private def computeDenot(lastd: SymDenotation)(using Context): SymDenotation = { + // Written that way do that it comes in at 32 bytes and is therefore inlineable for + // the JIT (reputedly, cutoff is at 35 bytes) util.Stats.record("Symbol.computeDenot") val now = ctx.period - val prev = checkedPeriod checkedPeriod = now - if lastd.validFor.contains(now) then - lastd - else - val newd = recomputeDenot(lastd) - if newd.exists then - lastDenot = newd - else - checkedPeriod = prev - newd + if lastd.validFor.contains(now) then lastd else recomputeDenot(lastd) } /** Overridden in NoSymbol */ protected def recomputeDenot(lastd: SymDenotation)(using Context): SymDenotation = { util.Stats.record("Symbol.recomputeDenot") - lastd.current.asSymDenotation + val newd = lastd.current.asInstanceOf[SymDenotation] + lastDenot = newd + if !newd.exists && lastd.initial.validFor.firstPhaseId > ctx.phaseId then + // We are trying to bring forward a symbol that is defined only at a later phase + // (typically, a nested Java class, invisible before erasure). + // In that case, keep the checked period to the previous validity, which + // means we will try another bring forward when the symbol is referenced + // at a later phase. Otherwise we'd get stuck on NoDenotation here. + // See #15562 and test i15562b in ReplCompilerTests + checkedPeriod = lastd.initial.validFor + newd } /** The original denotation of this symbol, without forcing anything */ @@ -771,7 +774,7 @@ object Symbols extends SymUtils { cls: ClassSymbol, name: TermName = nme.WILDCARD, selfInfo: Type = NoType)(using Context): TermSymbol = - newSymbol(cls, name, SelfSymFlags, selfInfo orElse cls.classInfo.selfType, coord = cls.coord) + newSymbol(cls, name, SelfSymFlags, selfInfo.orElse(cls.classInfo.selfType), coord = cls.coord) /** Create new type parameters with given owner, names, and flags. * @param boundsFn A function that, given type refs to the newly created @@ -938,7 +941,7 @@ object Symbols extends SymUtils { */ def getPackageClassIfDefined(path: PreName)(using Context): Symbol = staticRef(path.toTypeName, isPackage = true, generateStubs = false) - .disambiguate(_ is PackageClass).symbol + .disambiguate(_.is(PackageClass)).symbol def requiredModule(path: PreName)(using Context): TermSymbol = { val name = path.toTermName From ddfc4d3b8dd3b9a149b884acd4696317802f9805 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 26 Feb 2024 15:58:30 +0100 Subject: [PATCH 39/40] Avoid setting lastDenot to NoDenotation in the forward reference scenario This brings back an element of the original solution. [Cherry-picked 5631d76f247c3d02e21eeab135b7917f30f21394] --- compiler/src/dotty/tools/dotc/core/Symbols.scala | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 40528f2f0e5a..f00946782981 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -117,15 +117,16 @@ object Symbols extends SymUtils { protected def recomputeDenot(lastd: SymDenotation)(using Context): SymDenotation = { util.Stats.record("Symbol.recomputeDenot") val newd = lastd.current.asInstanceOf[SymDenotation] - lastDenot = newd - if !newd.exists && lastd.initial.validFor.firstPhaseId > ctx.phaseId then + if newd.exists || lastd.initial.validFor.firstPhaseId <= ctx.phaseId then + lastDenot = newd + else // We are trying to bring forward a symbol that is defined only at a later phase // (typically, a nested Java class, invisible before erasure). - // In that case, keep the checked period to the previous validity, which - // means we will try another bring forward when the symbol is referenced - // at a later phase. Otherwise we'd get stuck on NoDenotation here. + // In that case, keep lastDenot as it was and set the checked period to lastDenot's + // previous validity, which means we will try another bring forward when the symbol + // is referenced at a later phase. Otherwise we'd get stuck on NoDenotation here. // See #15562 and test i15562b in ReplCompilerTests - checkedPeriod = lastd.initial.validFor + checkedPeriod = lastd.validFor newd } From deaaf0d2892c233eb5908acc467e9be660a4295a Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 26 Feb 2024 18:07:42 +0100 Subject: [PATCH 40/40] Update compiler/src/dotty/tools/dotc/core/Symbols.scala [Cherry-picked 46c50e7ba25554f2ba7f9a422fb9109d5387348e] --- compiler/src/dotty/tools/dotc/core/Symbols.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index f00946782981..ad2b3bfe527d 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -105,7 +105,7 @@ object Symbols extends SymUtils { } private def computeDenot(lastd: SymDenotation)(using Context): SymDenotation = { - // Written that way do that it comes in at 32 bytes and is therefore inlineable for + // Written that way so that it comes in at 32 bytes and is therefore inlineable for // the JIT (reputedly, cutoff is at 35 bytes) util.Stats.record("Symbol.computeDenot") val now = ctx.period